c++reflectionnoexceptstructured-bindingsc++26

Checking noexceptness when applying function to a pack


I have the following C++26 function which runs some code for each field of a structure:

template <typename S, typename F>
constexpr auto for_each_field(S&& s, F&& f) 
{
  auto&& [... elts] = static_cast<S&&>(s);
  ((static_cast<F&&>(f)(std::forward_like<S>(elts))), ...);
}

I would like to enable noexcept conditionally, based on whether f can itself throw an exception.

But I am being stopped in my tracks by the fact that at least two expressions are required to check for noexceptness here: one for the destructuring, the second for checking each individual call.

e.g. the "theoretic" code would have to look somehow like

template <typename S, typename F>
constexpr auto for_each_field(S&& s, F&& f) noexcept(noexcept( 
  auto&& [... elts] = static_cast<S&&>(s);
  ((static_cast<F&&>(f)(std::forward_like<S>(elts))), ...);
))
{
  auto&& [... elts] = static_cast<S&&>(s);
  ((static_cast<F&&>(f)(std::forward_like<S>(elts))), ...);
}

but that's of course not possible.

Thus, are there any simple options to achieve this in the current state of C++26? Ideally a solution that works with current clang-21 would be great to enable me to test, which can be tried readily from godbolt: https://gcc.godbolt.org/z/EEGxY5zeq


Solution

  • The first thing that comes to mind is making another function to compute the noexceptness, which you can encode in the return type:

    template <typename S, typename F>
    auto is_noexcept_callable(S&& s, F&& f)
    {
        auto&& [... elts] = static_cast<S&&>(s);
        return std::bool_constant<(noexcept(static_cast<F&&>(f)(std::forward_like<S>(elts))) && ...)>{};
    }
    

    And then you can pass the result to noexcept(...):

    template <typename S, typename F>
    constexpr auto for_each_field(S&& s, F&& f) noexcept(decltype((is_noexcept_callable)(std::forward<S>(s), std::forward<F>(f))){})
    {
        // ...
    }
    

    As noted by @康桓瑋 in the comments, you also need to separately check the noexcepness of get<I>(...), but that's a simple exercise in using std::make_index_sequence, so I'll leave that as an exercise to the reader.

    A structured binding uses get only if std::tuple_size<T> is defined, so check for that first. Otherwise the decomposition mechanism can't throw.