Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concatenating tuples as types

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.).

like image 655
egst Avatar asked Nov 20 '18 13:11

egst


People also ask

Can you concatenate tuples?

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.

How do you connect two tuples together?

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.


3 Answers

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};
like image 96
Red.Wave Avatar answered Oct 17 '22 18:10

Red.Wave


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...>;
};
like image 6
felix Avatar answered Oct 17 '22 18:10

felix


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...>; };
like image 4
max66 Avatar answered Oct 17 '22 18:10

max66