I'm trying to practice some template programming. Maybe there's a standard way to do this, and I would be thankful for such answers, but my main goal is to practice the template programming techniques, so I tried to implement it myself:
I need to concatenate multiple tuples, but as types, not like std::cat_tuple
does it. So I need something like cat<std::tuple<int, float>, std::tuple<char, bool>, ...>
to get std::tuple<int, float, char, bool, ...>
as a type.
My current attempt failed with a is not a template
error:
/* Concat tuples as types: */
template <typename first_t, typename... rest_t> struct cat {
using type = typename _cat<first_t, typename cat<rest_t...>::type>::type;
^^^^ cat is not a template
};
template <typename first_t, typename second_t>
struct cat<first_t, second_t> {
using type = typename _cat<first_t, second_t>::type;
^^^^ cat is not a template
};
// Concat two tuples:
template <typename, typename> struct _cat;
template <typename tuple_t, typename first_t, typename... rest_t>
struct _cat<tuple_t, std::tuple<first_t, rest_t...>> {
using type = typename _cat<typename append<first_t, tuple_t>::type, std::tuple<rest_t...>>::type;
};
template <typename tuple_t, typename first_t>
struct _cat<tuple_t, std::tuple<first_t>> {
using type = typename append<first_t, tuple_t>::type;
};
// Prepend element to tuple:
template <typename, typename> struct prepend;
template <typename elem_t, typename... tuple_elem_t>
struct prepend<elem_t, std::tuple<tuple_elem_t...>> {
using type = std::tuple<elem_t, tuple_elem_t...>;
};
// Apppend element to tuple:
template <typename, typename> struct append;
template <typename elem_t, typename... tuple_elem_t>
struct append<elem_t, std::tuple<tuple_elem_t...>> {
using type = std::tuple<tuple_elem_t..., elem_t>;
};
What may be causing the error?
Is this a good approach? It might be solved in a simpler way, but I wanted it to be multi-purpose (with the append/prepend operations etc.).
When it is required to concatenate multiple tuples, the '+' operator can be used. A tuple is an immutable data type. It means, values once defined can't be changed by accessing their index elements.
Concatenation is done with the + operator, and multiplication is done with the * operator. Because the + operator can concatenate, it can be used to combine tuples to form a new tuple, though it cannot modify an existing tuple. The * operator can be used to multiply tuples.
How about a one-liner direct template aliase:
template<typename ... input_t>
using tuple_cat_t=
decltype(std::tuple_cat(
std::declval<input_t>()...
));
tuple_cat_t<
std::tuple<int,float>,
std::tuple<int>
> test{1,1.0f,2};
After reordering the definition a little, your code works fine.
I don't think that there are any guidelines for template meta programming. Probably due to the fact that the C++ committee is enhancing TMP "aggressively" and too few people is using TMP.
Here is my version of Cat
, it basically follows the same structure as yours:
template <class, class>
struct Cat;
template <class... First, class... Second>
struct Cat<std::tuple<First...>, std::tuple<Second...>> {
using type = std::tuple<First..., Second...>;
};
Is too late to play?
I propose the following solution
template <typename T, typename ...>
struct cat
{ using type = T; };
template <template <typename ...> class C,
typename ... Ts1, typename ... Ts2, typename ... Ts3>
struct cat<C<Ts1...>, C<Ts2...>, Ts3...>
: public cat<C<Ts1..., Ts2...>, Ts3...>
{ };
Observe that this solution doesn't works only with a variadic list of std::tuple
but also with a generic container of types. If a std::tuple
-only solution is enough for you, you can simplify it as follows
template <typename T, typename ...>
struct cat
{ using type = T; };
template <typename ... Ts1, typename ... Ts2, typename ... Ts3>
struct cat<std::tuple<Ts1...>, std::tuple<Ts2...>, Ts3...>
: public cat<std::tuple<Ts1..., Ts2...>, Ts3...>
{ };
You can test that works with
using t1 = typename cat<std::tuple<int, float>,
std::tuple<char, bool>,
std::tuple<long, char, double>>::type;
using t2 = std::tuple<int, float, char, bool, long, char, double>;
static_assert(std::is_same<t1, t2>::value, "!");
-- EDIT --
As pointed by felix
(thanks!) with my precedent solution we have that
std::is_same<int, typename cat<int>::type>::value == true
that is... cat<T>::type
is defined also when T
isn't a std::tuple
.
This is a problem?
I don't know because I don't know how is used cat<T>::type
.
Anyway... avoid it imposing that cat<Ts...>::type
is defined only when all Ts...
are type containers (with the same container), it's simple: the main version for cat
become only declared but not defined
template <typename, typename ...> // or also template <typename...>
struct cat;
and is introduced an additional specialization with a single type (but only when it's a type container).
template <template <typename ...> class C, typename ... Ts1>
struct cat<C<Ts1...>>
{ using type = C<Ts1...>; };
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With