Search code examples
c++for-looptemplatesvariadic-templatesstdtuple

Iterate std::tuple indices with lambda


There are a lot of approaches how to iterate trough std::tuple. And it is similar to range-based-for loop. I want to do something like this, but with indices of tuple, to get access to elements of various tuples.

For example I have tuple of different types, but all of them has same free functions / operators std::tuple<float, std::complex, vec3, vec4> and I want to do some operation between two or more such tuples.

I tried to write something like this:

template<typename Lambda, typename... Types, int... Indices>
void TupleIndexElems_Indexed(TTuple<Types...>, Lambda&& Func, TIntegerSequence<int, Indices...>)
{
    Func.template operator()<Indices...>();
}

template<typename TupleType, typename Lambda>
void TupleIndexElems(Lambda&& Func)
{
    TupleIndexElems_Impl(TupleType{}, Func);
}


template<typename... Types, typename Lambda>
void TupleIndexElems_Impl(TTuple<Types...>, Lambda&& Func)
{
    TupleIndexElems_Indexed(TTuple<Types...>{}, Func, TMakeIntegerSequence<int, sizeof...(Types)>{});
}

Usage:

    FSkyLightSettings& operator+=(FSkyLightSettings& Other)
    {
        auto Tup1 = AsTuple();
        auto Tup2 = Other.AsTuple();

        using TupType = TTuple<float*, FLinearColor*, FLinearColor*>;

        auto AddFunc = [] <typename Tup, int Index> (Tup t1, Tup t2)
        {
            *t1.template Get<Index>() = (*t1.template Get<Index>()) + (*t2.template Get<Index>());
        };
            
        TupleIndexElems<TupType>([=]<int... Indices>
        {
            AddFunc.template operator()<TupType, Indices>(Tup1, Tup2); // How to fold it?
        });
        return *this;
    }

I thought the best way to do it is using variaic lambda template, but when I tried to call it, I confused about impossibility to use fold expression.

Are there any elegant solutions to do that (for various versions of C++)?

UPD: I've also tried to use recursive lambda, but I can't due to compiler error C3536:

    auto PlusVariadic = [=]<int Index, int... Indices>
    {
        Plus.template operator()<TupType, Index>(Tup1, Tup2); // How to fold it?
        if constexpr (Index != 0)
        {
            PlusVariadic.operator()<Indices...>();
        }
    };

Solution

  • One convenient way in C++20 I use to iterate tuples is to create a constexpr_for function that calls a lambda with a std::integral_constant parameter to allow indexing, as described in my Achieving 'constexpr for' with indexing post.

    #include <utility>
    #include <type_traits>
    
    template<size_t Size, typename F>
    constexpr void constexpr_for(F&& function) {
        auto unfold = [&]<size_t... Ints>(std::index_sequence<Ints...>) {
            (std::forward<F>(function)(std::integral_constant<size_t, Ints>{}), ...);
        };
    
        unfold(std::make_index_sequence<Size>());
    }
    

    example usage:

    #include <tuple>
    #include <iostream>
    
    int main() {
        auto Tup1 = std::make_tuple(1, 2.0, 3ull, 4u);
        auto Tup2 = std::make_tuple(1ull, 2.0f, 3.0, (char)4);
    
        constexpr auto size = std::tuple_size_v<decltype(Tup1)>;
    
        constexpr_for<size>([&](auto i) {
            std::get<i>(Tup1) += std::get<i>(Tup2);
            std::cout << "tuple<" << i << "> = " << std::get<i>(Tup1) << '\n';
        });
    }
    

    Output:

    tuple<0> = 2
    tuple<1> = 4
    tuple<2> = 6
    tuple<3> = 8
    

    Try it out on godbolt.