可变类型列表前缀的 c++ std::tuple

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

我正在尝试从一些可变类型列表中提取类型前缀。这是我的尝试:

#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> >;
}

这在 gcc 8.2 上失败:

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

第二个专业似乎比第一个更具体,所以我不明白为什么这里会出现歧义。 我做错了什么?

P.S。这在 clang 7.0 上也失败并出现类似错误,但似乎适用于 icc 19.0.1 和 msvc 19.16。

经过更多研究,这是我的发现:

这些是 Partial Ordering 规则:

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

并且:

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

AB 分别成为我的代码中的第一个和第二个特化。 A 接受数字 n 大于 0 的结构(B 不接受)。另一方面,B 接受前缀包后带有 0 类型的结构(A 不接受)。因此 AB 都不是 "most specialized",程序不应该编译。 也就是icc和msvc是错误的


可能的解决方案:

假设我添加了评论中提到的以下第三个专业化(称之为 C):

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

C 不接受大于 0 的数字 n 或前缀包后具有 0 类型的结构。因此它是最专业的。此外,如果n==0C不能使用,A也不能使用,所以这解决了[=36=之间的歧义]A 和 B.

添加此代码后,代码可与 gcc、clang 和 msvc 一起使用,但 icc 拒绝它并出现以下错误:

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

正如我之前提到的,第一个 (C) 比第二个 (B) 更专业,所以我必须推断icc又错了

作为替代方案,您可以使用 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