Concatenating tuples as types
Asked Answered
A

3

12

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

Archaeological answered 20/11, 2018 at 13:29 Comment(4)
_cat is undefined at the point of usage (it is defined only after the templates, that use it).Dwarfish
After reordering the definition a little, it works fine. Your approach looks fine to me.Mercia
Ah sure... Those were previously implemented as methods inside a class, so the order didn't matter. Thanks. Sometimes the problem is just too simple to be found after hours of staring at the screen and trying to find a complex problem.Archaeological
"Maybe there's a standard way to do this" There is. You can do decltype on the result of std::cat_tuple.Coenobite
M
8

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...>;
};
Mercia answered 20/11, 2018 at 13:45 Comment(0)
A
12

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};
Auvergne answered 20/11, 2018 at 17:55 Comment(4)
I feel like an idiot. Yes: was so incredibly simple.Zealot
Missing simple things is a very common habit of any active minds.😜Auvergne
Nice. Similar solution was proposed by @Coenobite in the comments, but I didn't know about the declval function. Without it it wouldn't be possible to solve this without constructing the types.Archaeological
One addition is that I fixed a typo: its tuple_cat (not cat_tuple). declval is not a keyword; without it we'd need to copy its implementation (which is of-course a bit tricky).Auvergne
M
8

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...>;
};
Mercia answered 20/11, 2018 at 13:45 Comment(0)
Z
4

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...>; };
Zealot answered 20/11, 2018 at 14:0 Comment(7)
@JeJo - well... doesn't answer the first question ("What may be causing the error?") but I hope is a decent answer for the second one.Zealot
That's an interesting solution. It's a bit beyond of my experience with template programming, but I guess it's a reason to keep practicing. ThanksArchaeological
Stylish! But wouldn't that cat<int>::type works potentially makes the compilation fail too late?Mercia
@Mercia - Interesting doubt. Well, cat<int>::type is defined and is int. This can be a problem? I don't know because I don't know where is used cat<int>::type. If used in a context where is required only a std::tuple, the fail should be immediate and relativeli simple to understand. If used in a context where a int isn't an error... I suppose almost everything can happens. Uhmmm... Maybe I can propose a version that check if first element is a std::tuple (but losing elegance :( ).Zealot
Yes, I think that you sacrifice some restriction for elegance too. Not necessarily immediately though, it may pass several layers of template and fail miles away where it is eventually used. Flexibility comes with a price, just like void *.Mercia
@McSim - answer improved following the felix's doubt.Zealot
@McSim - give also a look at Red.Wave's answer: completely different but extremely simple.Zealot

© 2022 - 2024 — McMap. All rights reserved.