Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ std::tuple of a variadic type list prefix

I'm attempting to extract a prefix of types from some variadic type list. Here is my attempt:

#include <tuple>
#include <type_traits>

template <typename... Ts>
struct pack{};

template <size_t n, typename... Args>
struct prefix_tuple;

template <size_t n, typename... TPrefix, typename Tnext, typename... Ts>
struct prefix_tuple<n, pack<TPrefix...>, Tnext, Ts...>{
    using type = typename 
        prefix_tuple<n-1, pack<TPrefix..., Tnext>, Ts...>::type;
};

template <typename... TPrefix, typename... Ts>
struct prefix_tuple<0, pack<TPrefix...>, Ts...>{
    using type = std::tuple<TPrefix...>;
};

template <size_t n, typename... Args>
using prefix_tuple_t = typename 
    prefix_tuple<n, pack<>, Args...>::type;

bool f(){
    return std::is_same_v<prefix_tuple_t<2, int, char, double>,
                          std::tuple<int, char> >;
}

This fails on gcc 8.2 with:

error: ambiguous template instantiation for 'struct prefix_tuple<0, pack< int, char>, double>'

The second specialization appears to be more specific than the first, so I don't see why there's ambiguity here. What am I doing wrong?

P.S. This also fails on clang 7.0 with a similar error, but appears to work on icc 19.0.1 and msvc 19.16.

like image 673
Benny K Avatar asked Mar 17 '19 13:03

Benny K


Video Answer


2 Answers

After some more research, here are my findings:

These are the Partial Ordering rules:

1) If only one specialization matches the template arguments, that specialization is used.
2) If more than one specialization matches, partial order rules are used to determine which specialization is more specialized. The most specialized specialization is used, if it is unique (if it is not unique, the program cannot be compiled).
3) If no specializations match, the primary template is used

And:

Informally "A is more specialized than B" means "A accepts a subset of the types that B accepts".

Let A and B be the first and second specializations in my code, respectively. A accepts structs with numbers n grater than 0 (which B does not). On the other hand, B accepts structs with 0 types following the prefix pack (which A does not). Therefore neither A nor B is "most specialized", and the program should not compile. That is, icc and msvc are wrong.


Possible solution:

Suppose I add the following third specialization (call it C) mentioned in my comment:

template <typename... TPrefix, typename Tnext, typename... Ts>
struct prefix_tuple<0, pack<TPrefix...>, Tnext, Ts...>{
    using type = std::tuple<TPrefix...>;
};

C does not accept either numbers n grater than 0, or structs with 0 types follwing the prefix pack. It is therefore the most specialized. Furthermore, if n==0 and C can't be used, neither can A, so this resolves the ambiguity between A and B.

With this addition, the code works with gcc, clang and msvc, but icc rejects it with the following error:

error: more than one partial specialization matches the template argument
list of class "prefix_tuple<0UL, pack < int, char >, double>":
"prefix_tuple<0UL, pack < TPrefix... >, Tnext, Ts...>"
"prefix_tuple<0UL, pack < TPrefix... >, Ts...>"

As I mentioned earlier, the first of these (C) is more specialized than the second (B), so I must deduce that icc is wrong again.

like image 189
Benny K Avatar answered Sep 28 '22 00:09

Benny K


As alternative, you might use std::index_sequence:

template <typename Seq, typename Tuple> struct prefix_tuple_impl;

template <std::size_t ... Is, typename Tuple>
struct prefix_tuple_impl<std::index_sequence<Is...>, Tuple>
{
    using type = std::tuple<std::tuple_element_t<Is, Tuple>...>;
};

template <std::size_t N, typename ... Ts>
using prefix_tuple_t = typename prefix_tuple_impl<std::make_index_sequence<N>,
                                                  std::tuple<Ts...>>::type;

Demo

like image 28
Jarod42 Avatar answered Sep 28 '22 00:09

Jarod42