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

Upvotes: 3

Views: 66

Answers (1)

HolyBlackCat
HolyBlackCat

Reputation: 96791

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.

Upvotes: 1

Related Questions