c++templatesconstexprfunction-templatesnested-for-loop

Loop over pairs from 2 constexpr arrays to instantiate and run template function


C++ version for project is 20.

Suppose we have some service class template:

template<int someSegmentSize>
class SomeAbstractFileService {
public:
    void init() {
        std::cout << "Initializing FileService with segment size: " << someSegmentSize << std::endl;
    }

    template<int HashNum>
    void create_shemas() {
        std::cout << "Creating  with hash function number: " << HashNum << " and segment size: " << someSegmentSize << std::endl;
    }
    //some other non template functions ...
};

and 2 constexpr arrays(currently filled with arbitrary values):

constexpr size_t N=7;
constexpr size_t N2=7;//sizes may differ
constexpr std::array<int,N> segment_size_arr{-1, -2, -3, -4, -5, -6, -7};
constexpr std::array<int,N2> hash_num_arr{1, 2, 3, 4, 5, 6, 7};

For testing purposes i want to iterate through all pairs of values of both arrays (like (-1,1),(-1,2),...,(-1,7),(-2,1),...,(-7,7)) to instantiate and call this function template:

template<int SegmentSize,int Hash>
void performStuff()
{
    std::cout<<SegmentSize<<'\t'<<Hash<<'\n';
    SomeAbstractFileService<SegmentSize> ss;
    ss.init();
    ss.template create_shemas<Hash>();//this function must be called with new object of SomeAbstractFileService otherwise schema update will collide with existing one
}

This is one of my attempts using folds and lambda expressions(inspired by solution):

#include<iostream>
#include<array>


template<int someSegmentSize>
class SomeAbstractFileService {
public:
    void init() {
        std::cout << "Initializing FileService with segment size: " << someSegmentSize << std::endl;
    }

    template<int HashNum>
    void create_shemas() {
        std::cout << "Creating  with hash function number: " << HashNum << " and segment size: " << someSegmentSize << std::endl;
    }
    //some other non template functions ...
};



template<int SegmentSize,int Hash>
void performStuff()
{
    std::cout<<SegmentSize<<'\t'<<Hash<<'\n';
    SomeAbstractFileService<SegmentSize> ss;
    ss.init();
    ss.template create_shemas<Hash>();

}

template<typename T,size_t N,size_t N2,const std::array<T,N> segment_size_arr,const std::array<T,N2> hash_num_arr>
constexpr void iterate_pairs() {
    []<std::size_t... Is>(std::index_sequence<Is...> is) {

        (([]<std::size_t val,std::size_t... Is2>(std::index_sequence<Is...> is,std::index_sequence<Is2...> is2)
        {
            constexpr T segsize=segment_size_arr[val];//some caveman check
            (performStuff<segment_size_arr[val],hash_num_arr[Is2]>(),...);


        }<Is>(is,std::make_index_sequence<N2>{})),...);

    }(std::make_index_sequence<N>{});
}



int main(){
    constexpr size_t N=7;
    constexpr size_t N2=7;//sizes may differ
    constexpr std::array<int,N> segment_size_arr{-1, -2, -3, -4, -5, -6, -7};//actually may be global variable
    constexpr std::array<int,N2> hash_num_arr{1, 2, 3, 4, 5, 6, 7};//actually may be global variable

    iterate_pairs<int, N, N2, segment_size_arr, hash_num_arr>();

}

but this one results in an error:

Invalid operands to binary expression ('(lambda at /.../main.cpp:39:11)' and 'unsigned long') main2.cpp:35:5: note: in instantiation of function template specialization 'iterate_pairs()::(anonymous class)::operator()<0UL, 1UL, 2UL, 3UL, 4UL, 5UL, 6UL>' requested here main2.cpp:58:5: note: in instantiation of function template specialization 'iterate_pairs<int, 7UL, 7UL, std::array<int, 7UL>{{-1, -2, -3, -4, -5, -6, -7}}, std::array<int, 7UL>{{1, 2, 3, 4, 5, 6, 7}}>' requested here

What will be a proper way to perform such iteration? Can this one be generalized for arbitrary number of constexpr arrays(all triplets, quads ...)?

Update

After some time and practice created my "frankenstein" code from answers by users edrezen and Kirisame Igna.

Code


Solution

  • As an extension of my previous answer, it is possible to consider that each item to be iterated is just a point in a tensor whose K dimensions are provided by the dimensions of each input array.

    Since we know the number of items in the tensor (the product N of the dimension of each array), we can construct an array holding all the items of the tensor, which in the end is the wanted output.

    Indeed, one can iterate from 1 to N and then retrieve the corresponding K indexes in the tensor and we are done.

    In other words, we can compute the Cartesian product by a two-loops iteration. Proceeding this way may require less metaprogramming complex code than other approaches.

    #include <tuple>
    #include <array>
    #include <type_traits>
    
    ////////////////////////////////////////////////////////////////////////////////
    template<typename...ARRAYS>
    constexpr auto cartesian_product (ARRAYS...arrays)
    {
        // we define the type of an entry
        using type = std::tuple<typename ARRAYS::value_type...>;
    
        // we define the number of entries
        constexpr std::size_t N = (1 * ... * arrays.size());
    
        // we compute the dimensions of the successive parts of the tensor
        std::array<std::size_t,sizeof...(arrays)> dims { arrays.size()... };
        for (std::size_t i=1; i<dims.size(); ++i)  { dims[i] *= dims[i-1]; }
            
        return [&] () 
        {
            // the result is an array holding N tuples.
            std::array<type, N> result;
    
            // we iterate each entry of the result array.
            for (std::size_t i=0; i<result.size(); ++i)
            {
                [&]<std::size_t... Is>(std::index_sequence<Is...>) 
                {
                   // we demultiplex the current index for each array from the global index 'i'
                   auto idx = std::make_tuple ( ( (i*std::get<Is>(dims)) / N) % arrays.size() ...);
       
                   // we set the required values for the current entry.             
                   result[i] = std::make_tuple (arrays[std::get<Is>(idx)]...);
                
                }(std::make_index_sequence<sizeof...(arrays)>{});
            }
    
            return result;
        }();
    }
    
    ////////////////////////////////////////////////////////////////////////////////
    constexpr std::array<int,   3> a1  {1, 2, 3}; 
    constexpr std::array<char,  2> a2  {'a','b'}; 
    constexpr std::array<double,4> a3  {2.0, 4.0, 6.0, 8.0}; 
    
    constexpr auto entries = cartesian_product (a1,a2,a3);
    
    static_assert (entries.size()==24);
    static_assert (std::is_same_v<decltype(entries)::value_type, std::tuple<int,char,double>>);
    
    static_assert (entries[ 0] == std::make_tuple (1, 'a', 2.0) );
    static_assert (entries[ 1] == std::make_tuple (1, 'a', 4.0) );
    static_assert (entries[ 2] == std::make_tuple (1, 'a', 6.0) );
    static_assert (entries[ 3] == std::make_tuple (1, 'a', 8.0) );
    
    static_assert (entries[ 4] == std::make_tuple (1, 'b', 2.0) );
    static_assert (entries[ 5] == std::make_tuple (1, 'b', 4.0) );
    static_assert (entries[ 6] == std::make_tuple (1, 'b', 6.0) );
    static_assert (entries[ 7] == std::make_tuple (1, 'b', 8.0) );
    
    static_assert (entries[ 8] == std::make_tuple (2, 'a', 2.0) );
    static_assert (entries[ 9] == std::make_tuple (2, 'a', 4.0) );
    static_assert (entries[10] == std::make_tuple (2, 'a', 6.0) );
    static_assert (entries[11] == std::make_tuple (2, 'a', 8.0) );
    
    static_assert (entries[12] == std::make_tuple (2, 'b', 2.0) );
    static_assert (entries[13] == std::make_tuple (2, 'b', 4.0) );
    static_assert (entries[14] == std::make_tuple (2, 'b', 6.0) );
    static_assert (entries[15] == std::make_tuple (2, 'b', 8.0) );
    
    static_assert (entries[16] == std::make_tuple (3, 'a', 2.0) );
    static_assert (entries[17] == std::make_tuple (3, 'a', 4.0) );
    static_assert (entries[18] == std::make_tuple (3, 'a', 6.0) );
    static_assert (entries[19] == std::make_tuple (3, 'a', 8.0) );
    
    static_assert (entries[20] == std::make_tuple (3, 'b', 2.0) );
    static_assert (entries[21] == std::make_tuple (3, 'b', 4.0) );
    static_assert (entries[22] == std::make_tuple (3, 'b', 6.0) );
    static_assert (entries[23] == std::make_tuple (3, 'b', 8.0) );
    
    int main() {}
    

    Demo