Reputation: 3
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 ...)?
After some time and practice created my "frankenstein" code from answers by users edrezen and Kirisame Igna.
Upvotes: 0
Views: 139
Reputation: 1626
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() {}
Upvotes: 0
Reputation: 401
First of all, about your questions.
Your way is the proper way of doing this AFAIK, though missing one single part; see next section.
To do this (compile-time cartesian product) on arbitrary number of constexpr arrays, you can utilize a simple recursion, and tons of template metaprogramming shit:
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <array>
#include <functional>
#include <iostream>
template <auto Val>
struct val_wrapper {
static constexpr auto value = Val;
};
template <class... T>
struct type_list;
template <class First, class... Rest>
struct type_list<First, Rest...> {
using head = First;
using tail = type_list<Rest...>;
template <class T>
using append = type_list<First, Rest..., T>;
static constexpr bool empty = false;
};
template <>
struct type_list<> {
template <class T>
using append = type_list<T>;
static constexpr bool empty = true;
};
template <auto Func, auto HeadArr, typename TailArr, typename HistoryVals>
constexpr void product_impl() {
if constexpr (TailArr::empty) {
constexpr size_t len_of_current = HeadArr.size();
[]<size_t... Idx>(std::index_sequence<Idx...>) {
constexpr auto with_i = []<size_t I> {
using current_vals = typename HistoryVals::template append<
val_wrapper<HeadArr[I]>>;
[]<auto... Vals>(type_list<val_wrapper<Vals>...>) {
std::invoke(Func, Vals...);
}(current_vals{});
};
((with_i.template operator()<Idx>()), ...);
}(std::make_index_sequence<len_of_current>{});
} else {
constexpr size_t len_of_current = HeadArr.size();
[]<size_t... Idx>(std::index_sequence<Idx...>) {
(product_impl<Func, TailArr::head::value, typename TailArr::tail,
typename HistoryVals::template append<
val_wrapper<HeadArr[Idx]>>>(),
...);
}(std::make_index_sequence<len_of_current>{});
}
}
template <auto Func, auto... Arrs>
constexpr void product() {
if constexpr (sizeof...(Arrs) == 0) {
return;
} else {
[]<auto HeadArr, auto... TailArr> {
product_impl<Func, HeadArr, type_list<val_wrapper<TailArr>...>,
type_list<>>();
}.template operator()<Arrs...>();
}
}
int main() {
constexpr std::array v1{-1, -2};
constexpr std::array v2{1, 2};
constexpr std::array v3{3, 4};
constexpr auto print_all = [](auto... values) {
fmt::println("[{}]", fmt::join({values...}, ", "));
};
product<print_all, v1, v2, v3>();
}
https://godbolt.org/z/WE5bze9db
I used fmt for demonstration. I'd very like to explain how this works, but it's already late enough for me to worry about my health here, so I guess I have to write it later even if you're interested in it. Sorry 'bout that.
And then, about the error: I formatted your code, and things immediately became clear.
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];
(performStuff<segment_size_arr[val], hash_num_arr[Is2]>(), ...);
} < Is > (is, std::make_index_sequence<N2>{})), // noticed this line?
...);
}(std::make_index_sequence<N>{});
}
The lambda itself is not a function (and thus not a template function either), but rather a object of a anonymous class, with a operator()
that has the same signature as the lambda's, and has the same implementation as the lambda's body; this is the reason why lambdas are sometimes called as a kind of "syntactic sugar".
This gives the lambda object the ability to be called like a normal function; sometimes it is called a "functor" in the C++ world. In a templated lambda's case, the real templated thing is the operator()
on it, not the lambda "itself", so here the compiler thinks you are comparing the lambda and Is
. The correct way of writing this would be explicitly write out the operator()
, using the normal syntax to call a templated member function:
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];
(performStuff<segment_size_arr[val], hash_num_arr[Is2]>(), ...);
}.template operator()<Is>(is, std::make_index_sequence<N2>{})), // like this
...);
}(std::make_index_sequence<N>{});
}
The weird template
prefix is to tell the compiler that the dependent name operator()
is a template, for disambiguation. Check cppreference for more details, though it seems like you already know this.
Upvotes: 1
Reputation: 1626
Here is a possible solution. I don't think that iterate_pairs
needs two lambdas, one is enough to get the index of each item to be used for the two arrays if you accept to deal with an index that encapsulates the two required indexes
#include<iostream>
#include<array>
#include<iostream>
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()
{
static_assert (N>0 && N2>0);
[]<std::size_t... Is>(std::index_sequence<Is...>)
{
(performStuff<segment_size_arr[Is/N], hash_num_arr[Is%N2]>(),...);
}(std::make_index_sequence<N*N2>{});
}
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>();
}
Obviously, it can't be easily extended if one needs to use more than two entries.
Upvotes: 0