Given an std::tuple-like object (i.e. with defined tuple_size and get semantics) and a unary functor object ftor, I want to be able to call ftor on each element of the tuple-like object.
If I disregard the return value, I am aware of the int array trick:
namespace details {
template <typename Ftor, typename Tuple, size_t... Is>
void apply_unary(Ftor&& ftor, Tuple&& tuple, std::index_sequence<Is...>) {
using std::get;
int arr[] = { (ftor(get<Is>(std::forward<Tuple>(tuple))), void(), 0)... };
}
} // namespace details
template <typename Ftor, typename Tuple>
void apply_unary(Ftor&& ftor, Tuple&& tuple) {
details::apply_unary(std::forward<Ftor>(ftor),
std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size<Tuple>::value> {});
}
If I want the return values, I could replace the int [] trick with a call to std::make_tuple instead and return that. That would work provided that none of the calls to the ftor object have a void return value...
The question I have is therefore: considering I want to get the results of the call, how can I handle calls that might return void?
The only requirement is that I should get the results as a tuple and be able to tell which call lead to which element of the said result tuple.
As suggested by @Jarod42, wrapping the call with an additional layer that takes care of replacing void return with dummy struct will do the trick:
struct no_return {};
namespace details {
template <typename Ftor, typename Arg>
auto call(Ftor&& ftor, Arg&& arg)
-> std::enable_if_t<std::is_void<decltype(std::forward<Ftor>(ftor)(std::forward<Arg>(arg)))>::value, no_return> {
std::forward<Ftor>(ftor)(std::forward<Arg>(arg));
return no_return {};
}
template <typename Ftor, typename Arg>
auto call(Ftor&& ftor, Arg&& arg)
-> std::enable_if_t<!std::is_void<decltype(std::forward<Ftor>(ftor)(std::forward<Arg>(arg)))>::value, decltype(std::forward<Ftor>(ftor)(std::forward<Arg>(arg)))> {
return std::forward<Ftor>(ftor)(std::forward<Arg>(arg));
}
template <typename Ftor, typename Tuple, size_t... Is>
auto apply_unary(Ftor&& ftor, Tuple&& tuple, std::index_sequence<Is...>) {
using std::get;
return std::tuple<decltype(call(ftor, get<Is>(std::forward<Tuple>(tuple))))...> { call(ftor, get<Is>(std::forward<Tuple>(tuple)))... } ;
}
} // namespace details
template <typename Ftor, typename Tuple>
auto apply_unary(Ftor&& ftor, Tuple&& tuple) {
return details::apply_unary(std::forward<Ftor>(ftor),
std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple> >::value> {});
}
A live demo is available on Coliru
I did this using SFINAE to distinguish between the two overloads. It looks kinda ugly so if you have any improvement suggestion... I'm all ears!
Another way:
namespace details {
struct apply_unary_helper_t {};
template<class T>
T&& operator,(T&& t, apply_unary_helper_t) { // Keep the non-void result.
return std::forward<T>(t);
}
template <typename Ftor, typename Tuple, size_t... Is>
void apply_unary(Ftor&& ftor, Tuple&& tuple, std::index_sequence<Is...>) {
auto r = {(ftor(std::get<Is>(std::forward<Tuple>(tuple))), apply_unary_helper_t{})...};
static_cast<void>(r); // Suppress unused variable warning.
}
} // namespace details
template <typename Ftor, typename Tuple>
void apply_unary(Ftor&& ftor, Tuple&& tuple) {
details::apply_unary(std::forward<Ftor>(ftor),
std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tuple>>::value> {});
}
In the above, it applies operator, to the result of ftor and apply_unary_helper_t. If the result of ftor is void, then r is std::initializer_list<details::apply_unary_helper_t>, otherwise r is std::initializer_list<decltype(ftor(...))> which you can make use of.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With