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