10

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.

Rerito
  • 5,886
  • 21
  • 47
  • [I threw something together](https://wandbox.org/permlink/iKN8JZI6oVfNcIzM) using Boost Hana that does basically this using `hana::tuple`, but I don't consider it quite answer-worthy. Just posting this here in case someone wants to adapt it into an answer – Justin Nov 07 '17 at 09:16
  • You can build a mapping template that maps between tuple item indexes and result tuple item indexes. – user7860670 Nov 07 '17 at 09:20
  • 4
    Wrap functor call in a function which return dummy struct instead of void in void case – Jarod42 Nov 07 '17 at 09:20
  • Dup: https://stackoverflow.com/questions/1198260/iterate-over-tuple – lorro Nov 07 '17 at 10:29
  • Possible duplicate of [iterate over tuple](https://stackoverflow.com/questions/1198260/iterate-over-tuple) – lorro Nov 07 '17 at 10:30
  • 1
    @lorro That's not it, in the question you linked, return values are discarded. If at all, the question you link could be viewed as a subcase of my question – Rerito Nov 07 '17 at 10:57
  • @Rerito well, you can always wrap your function so that it takes an output arg instead of returning a value, it's just not that beautiful. – lorro Nov 07 '17 at 12:11

2 Answers2

4

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!

Rerito
  • 5,886
  • 21
  • 47
  • `std::invoke_result` might be shorter than `decltype`. and `typename Ret = /**/` might reduce a little the duplication. – Jarod42 Nov 07 '17 at 10:38
  • Note: if you want to guaranty order of evaluation, `make_tuple` should be replaced by `tuple<..>{..}` instead. – Jarod42 Nov 07 '17 at 10:40
  • Duly noted @Jarod42. I kept the `decltype` to stick with C++14 – Rerito Nov 07 '17 at 12:58
  • @Jarod42: `std::invoke_result` is unfortunately C++17 and up. @Rerito, you could make it a little shorter by using `std::is_void` instead of `std::is_same` – AndyG Nov 07 '17 at 13:33
2

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.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271