c++slicestdtuplestdapply

C++: Using std::apply to iterate through a tuple in 2 parts: up to an index applying a lambda and then after an index applying a different lambda


If I want to iterate through a tuple using std::apply but not apply one function to the entire thing, how can I separate the tuple, i.e. apply one function to the first n values and another to all values after it?

some_values would be a tuple which could have any length and types and length_of_first_part (likely named a little less verbosely) would known at compile-time.

std::tuple<char, long long, double, long double, float> some_values(33, 2, 3.4, 5.6, 7.8);
const size_t length_of_first_part = 2;
std::apply(
    [](auto&&... current_val) {
        ((std::cout << "(Should be first part) " << current_val << "\n"), ...); //Would obviously do a litle more than cout, but this is just a minimal example
    }, some_values
);

std::apply(
    [](auto&&... current_val) {
        ((std::cout << "(Should be second part) " << current_val << "\n"), ...);
    }, some_values
);

Solution

  • Gave this a shot. Looking at the "possible implementation" here: https://en.cppreference.com/w/cpp/utility/apply

    It passes an std::index_sequence to a helper function to call std::invoke. I changed it to pass std::make_sequence<N> (N being the split index) instead of the tuple size. Then I passed the inverse (tuple size - N) to call std::invoke on the second function:

    template <int N, typename F1, typename F2, typename Tuple, size_t... I1s, size_t... I2s>
    decltype(auto) split_apply_impl(F1&& f1, F2&& f2, Tuple&& t, std::index_sequence<I1s...>, std::index_sequence<I2s...>) {
        std::invoke(std::forward<F1>(f1), std::get<I1s>(std::forward<Tuple>(t))...);
        return std::invoke(std::forward<F2>(f2), std::get<(I2s+N)>(std::forward<Tuple>(t))...);
    }
    
    template <size_t N, typename F1, typename F2, typename Tuple>
    decltype(auto) split_apply(F1&& f1, F2&& f2, Tuple&& t)
    {
        return split_apply_impl<N>(
            std::forward<F1>(f1), 
            std::forward<F2>(f2),
            std::forward<Tuple>(t),
            std::make_index_sequence<N>{},
            std::make_index_sequence<(std::tuple_size_v<std::remove_reference_t<Tuple>>-N)>{});
    }
    

    Usage:

    std::tuple<char, long long, double, long double, float> some_values(33, 2, 3.4, 5.6, 7.8);
    constexpr size_t length_of_first_part = 2;
    split_apply<length_of_first_part>(
        [](auto&&... current_val) { 
            ((std::cout << "(Should be first part) " << current_val << "\n"), ...); 
        },
        [](auto&&... current_val) {
            ((std::cout << "(Should be second part) " << current_val << "\n"), ...);
        },
        some_values);
    

    Proof of concept: https://godbolt.org/z/6dvMre