Reputation: 1321
Is there any syntax through which I can distribute a non-type parameter pack across the parameters of a parameter pack of templates, expecting non-type packs (of different sizes)? Since this is pretty confusing I believe that an example may help to clarify what I mean: https://godbolt.org/z/FaEGTV
template <typename T, int... I> struct Vec { };
struct A
{
template<template<typename, int...> typename... Container,
typename... Ts, int... Is>
A(Container<Ts,Is...>... );
};
A a(Vec<int, 0>{}, Vec<double, 0>{}); // ok
A b(Vec<int, 0, 1>{}, Vec<double, 0, 1>{}); // ok
A c(Vec<int, 0>{}, Vec<double, 0, 1>{}); // error
I want the line marked // error
to work with a syntax similar to what I have. Clearly it will work fine if I write a special constructor to handle this case. However I want this to work for any number of containers, without me having to spell it out explicitly for all possible cases. For example, if I have 2 containers a,b
, with index sets {0,1,2}
and {0,1,2,3}
the expansion should look like A(a[0],a[1],a[2], b[0],b[1],b[2],b[3])
.
I am aware that I could do this recursively, unpacking one container at a time, and delegating recursively to constructors expecting a sequence of only flat elements in the beginning. My question is whether this is feasible in a more elegant, efficient, and less verbose way.
Upvotes: 3
Views: 293
Reputation: 217338
I would simply do:
template <typename T>
struct is_container_type : std::false_type{};
template<template<typename, size_t...> typename C, typename T, size_t... Is>
struct is_container_type<C<T, Is...>> : std::true_type
{
// Potential `using` to retrieve C, T, Is...
};
struct A
{
template<typename... Cs, REQUIRES(is_container_type<Cs>::value && ...)>
A(Cs... cs)
{
((void)(std::cout << cs << "\n"), ...);
}
};
With multi_apply
(std::apply
version for multiple tuple) (you can found there), you might do:
struct A
{
template<typename... Ts, REQUIRES(!is_container_type<Ts>::value || ...)>
A(Ts... ts)
{
((std::cout << ts << " "), ...);
std::cout << std::endl;
}
template<typename... Cs, REQUIRES(is_container_type<Cs>::value && ...)>
A(Cs... cs) :
A(multiple_apply([](auto...args){ return A(args...); },
cs...) // if Cs is tuple-like
// as_tuple(cs)...) // convert Cs to tuple<int, /*..*/, int>
)
{
}
};
Upvotes: 1
Reputation: 66210
For example, if I have 2 containers
a,b
, with index sets{0,1,2}
and{0,1,2,3}
the expansion should look likeA(a[0],a[1],a[2], b[0],b[1],b[2],b[3])
.I am aware that I could do this recursively, unpacking one container at a time, and delegating recursively to constructors expecting a sequence of only flat elements in the beginning. My question is whether this is feasible in a more elegant, efficient, and less verbose way.
Do you accept a solution where the expansion give you a std::tuple
with a[0],a[1],a[2], b[0],b[1],b[2],b[3]
?
In this case, you can follows the Igor's suggestion, unpack the values in the containers and repack they in tuples and use std::tuple_cat()
to concatenate the tuples.
I mean... given a container/tuple converter as follows
template <template<typename, std::size_t...> typename C,
typename T, std::size_t... Is>
auto getTpl (C<T, Is...> const & v)
{ return std::make_tuple(v.data[Is]...); }
you can start write your constructor, calling a delegate constructor, as follows
template <typename ... Ts>
A (Ts const & ... ts) : A{ std::tuple_cat( getTpl(ts)... ) }
{ }
and the final constructor is
template <typename ... Ts>
A (std::tuple<Ts...> const & tpl)
{ /* do something with values inside tpl */ }
The following is a full compiling example
#include <iostream>
#include <string>
#include <tuple>
template <typename T, std::size_t ... Is>
struct Vec
{
T data [sizeof...(Is)] = { Is... };
T const & operator[] (std::size_t i) const
{ return data[i]; }
T & operator[] (std::size_t i)
{ return data[i]; }
};
template <template<typename, std::size_t...> typename C,
typename T, std::size_t... Is>
auto getTpl (C<T, Is...> const & v)
{ return std::make_tuple(v.data[Is]...); }
struct A
{
template <typename ... Ts>
A (std::tuple<Ts...> const & tpl)
{ /* do something with values inside tpl */ }
template <typename ... Ts>
A (Ts const & ... ts) : A{ std::tuple_cat( getTpl(ts)... ) }
{ }
};
int main ()
{
A a(Vec<int, 0>{}, Vec<double, 0>{}); // ok
A b(Vec<int, 0, 1>{}, Vec<double, 0, 1>{}); // ok
A c(Vec<int, 0>{}, Vec<double, 0, 1>{}); // ok, now
}
Upvotes: 1
Reputation: 1321
I managed to produce a somewhat inefficient solution, that builds a larger vector in the first argument with every recursive step, and then has also a constructor that accepts a single vector and expands that. Clearly this is not ideal, since N vectors are created with a total number of elements N^2/2
in the worst case. Here is an example implementation illustrating what I did: https://coliru.stacked-crooked.com/a/1f41f3793846cdb1
I tried a different version, where no new objects are constructed, however the compiler didn't manage to deduce the proper constructor for some reason (I supposed it's due to the multiple expansions) - this version is in the GROW_VECTOR 0
define in the example linked above.
Upvotes: 0