5
\$\begingroup\$

Intro:

Imagine you have defined addition on types A and B with function A add(A,A) and B add(B,B). Now, you would like to have an addition function on tuple of these types, like tuple<A,B> add(tuple<A,B>,tuple<A,B>).

When we generalize this problem, we would like to extend an existing function working on some types to a function working on tuples of these types.

I call this extension function static_container_functor and the following code demonstrate its use:

int main() {

  std::tuple<double, int> t1{3.14159, -1}, t2{2.71828, 2};

  auto print = [](auto x) { std::cout << x << " "; };
  auto tuple_print = static_container_functor(print);
  tuple_print(t1); // prints "3.14159 -1 " and returns void instead of std::tuple<void,void> which is not possible

  std::cout << std::endl;

  auto add       = [](auto &&x, auto &&... y) { return (x + ... + y); };
  auto tuple_add = static_container_functor(add);
  auto t3 = tuple_add(t1, t1, t2); // t3 is of type std::tuple<double,int>
  tuple_print(t3);                 // prints "9.00146 0 "

  return 0;
}

I hope that it is clear to you by now what I'm trying to solve.

One way to think about static_container_functor is that it is somewhat glorified for_each for std::tuple.

What I would like to know:

I would like to know how can I possibly improve my code and whether I'm missing handling some cases. Also, I'm have attempted to do perfect forwarding and I do not know how to test if it works.

Furthermore, I have not employed the standard trick with std::index_sequence in the main implementation of static_container_functor but did a small variation of it to keep all of the code together in one function. I would like to know what do you think about it?

How to handle constexpr? In the example, how can I achieve that the tuple t3 can be constexpr when t1 and t2 are constexpr?

The function is called static_container_functor and not tuple_functor because I would like to extend it such that is works on std::variant and std::array. Ideally on anything that implements std::get<>. Any tips in that direction would be great.

Right now I'm doing a partial check, with static_assert, that the input arguments are really std::tuples. Would you recommend using SFINAE like in this post? However, for that I would need thatstd::enable_if works with automatic return type deduction.

Code:

https://godbolt.org/g/eWKxLo

#include <array>
#include <iostream>
#include <tuple>

template <std::size_t... I>
constexpr auto integral_sequence_impl(std::index_sequence<I...>) {
  return std::make_tuple((std::integral_constant<std::size_t, I>{})...);
}

template <std::size_t N, typename Indices = std::make_index_sequence<N>>
constexpr auto integral_sequence = integral_sequence_impl(Indices{});

template <typename Op>
auto static_container_functor(Op &&op) {
  return [&op](auto &&c, auto &&... d) {
    /* here I should add a test that c and d... are really tuples */

    constexpr int N = std::tuple_size_v<std::remove_reference_t<decltype(c)>>;
    static_assert(
        (true == ... ==
         (N == std::tuple_size_v<std::remove_reference_t<decltype(d)>>)),
        "All of the arguments must have the same length!");

    auto implementation = [&](auto... I) {
      auto slice = [&](auto idx) {
        return std::forward_as_tuple(std::get<idx>(c), std::get<idx>(d)...);
      };

      auto result = [&](auto idx) {
        return std::apply(op, std::forward<decltype(slice(idx))>(slice(idx)));
      };
      auto zero = std::integral_constant<size_t, 0>{};

      if constexpr /* Are all return types are void? Return void*/
          ((std::is_same_v<void, decltype(result(I))> && ... && true)) {

        (result(I), ...);
        return;

      } else /* All other cases. Return std::tuple  */ {
        using ReturnType = std::tuple<decltype(result(I))...>;
        return ReturnType{result(I)...};
      }
    };

    return std::apply(implementation, integral_sequence<N>);
  };
}

int main() {

  std::tuple<double, int> t1{3.14159, -1}, t2{2.71828, 2};

  auto print = [](auto x) { std::cout << x << " "; };

  auto tuple_print = static_container_functor(print);
  tuple_print(t1); // prints "3.14159 -1 " and returns void

  std::cout << std::endl;

  auto add       = [](auto &&x, auto &&... y) { return (x + ... + y); };
  auto tuple_add = static_container_functor(add);
  auto t3 = tuple_add(t1, t1, t2); // t3 is of type std::tuple<double,int>
  tuple_print(t3);                 // prints "9.00146 0 "

  return 0;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.