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

max66
  • 65,235
  • 10
  • 71
  • 111
egst
  • 1,605
  • 3
  • 18
  • 25
  • 2
    `_cat` is undefined at the point of usage (it is defined only after the templates, that use it). – Algirdas Preidžius Nov 20 '18 at 13:32
  • 3
    After [reordering](http://coliru.stacked-crooked.com/a/1fee31b995533096) the definition a little, it works fine. Your approach looks fine to me. – felix Nov 20 '18 at 13:36
  • 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. – egst Nov 20 '18 at 13:54
  • 1
    *"Maybe there's a standard way to do this"* There is. You can do `decltype` on the result of `std::cat_tuple`. – HolyBlackCat Nov 20 '18 at 14:20

3 Answers3

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};
Red.Wave
  • 2,790
  • 11
  • 17
  • I feel like an idiot. Yes: was so incredibly simple. – max66 Nov 20 '18 at 18:09
  • 2
    Missing simple things is a very common habit of any active minds. – Red.Wave Nov 20 '18 at 18:11
  • Nice. Similar solution was proposed by @HolyBlackCat 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. – egst Nov 20 '18 at 18:49
  • 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). – Red.Wave Nov 20 '18 at 18:55
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...>;
};
felix
  • 2,213
  • 7
  • 16
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...>; };
max66
  • 65,235
  • 10
  • 71
  • 111
  • @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. – max66 Nov 20 '18 at 14:24
  • 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. Thanks – egst Nov 20 '18 at 14:24
  • Stylish! But wouldn't that `cat::type` works potentially makes the compilation fail too late? – felix Nov 20 '18 at 14:26
  • 1
    @felix - Interesting doubt. Well, `cat::type` is defined and is `int`. This can be a problem? I don't know because I don't know where is used `cat::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 :( ). – max66 Nov 20 '18 at 14:34
  • 1
    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 *`. – felix Nov 20 '18 at 14:40
  • @McSim - answer improved following the felix's doubt. – max66 Nov 20 '18 at 14:47
  • 1
    @McSim - give also a look at Red.Wave's answer: completely different but extremely simple. – max66 Nov 20 '18 at 18:15