具有非类型参数的部分模板特化:GCC 与 MSVS

Partial template specialization with non-type parameters: GCC vs MSVS

考虑这个简单的模板专业化:

template<typename T, size_t I>
struct S {};

template<typename T>
struct S<T, std::tuple_size<T>::value> {};

GCC 不编译它,因为它在模板参数 std::tuple_size<T>::value:

中使用模板参数 T

error: template argument 'std::tuple_size<_Tp>::value' involves template parameter(s)

现在让我们在 tuple_size 模板参数中用 typename std::remove_reference<T>::type 替换 T

// Using primary structure template from previous example.
template<typename T>
struct S<T, std::tuple_size<typename std::remove_reference<T>::type>::value> {};

此代码在模板参数中仍然使用模板参数,但 GCC 编译它时没有任何错误或警告。为什么?

现在,如果我们尝试使用带有 /std:c++latest 标志的 MSVS 编译第二个示例,它将停止并出现错误 C2755:

non-type parameter of a partial specialization must be a simple identifier

这是什么奇怪的限制?当 I 等于元组大小时,我想停止编译时递归。​​

那么他们谁错了:MSVS 还是 GCC?

请注意,即使没有任何模板实例化,MSVS 也会报告错误,而 GCC 可以很好地处理所有这些实例:

S<std::tuple<int, float>, 9> s1;
S<std::tuple<int, float>, 2> s2;
S<int, 42> s3;

我使用 MSVS Community 2015 Update 3 及其默认编译器和 GCC 6.2.1。

尝试了 Clang 3.8.0。它不会编译两个片段,并出现类似于 GCC 消息的错误:

error: non-type template argument depends on a template parameter of the partial specialization

处理部分 class 模板专业化可行性的标准的特定部分在过去几年中已更改多次。 [temp.class.spec.match] 中的原始限制为:

A partially specialized non-type argument expression shall not involve a template parameter of the partial specialization except when the argument expression is a simple identifier.

您的代码显然与此冲突,std::tuple_size<T>::value 不是标识符。

然后在 cwg issue 1315 之后更改为:

Each template-parameter shall appear at least once in the template-id outside a non-deduced context.

但我们没问题 - T 在 non-deduced 上下文中用作第一个模板参数。在 template auto 之后,它现在显示为:

If the template arguments of a partial specialization cannot be deduced because of the structure of its template-parameter-list and the template-id, the program is ill-formed.

但我们在那里也很好。可以推断,您有正确的 "structure" - 您的专业化在与主要相同的位置使用 non-type 模板参数,它们应该匹配得很好。


我觉得按照1315的解析(我觉得是post-C++14),代码应该是well-formed,但是gcc 和 clang 都拒绝它。一个不幸的修复是使用两个 type 参数来代替:

template <class T, class I>
struct S;

template<typename T>
struct S<T, typename std::tuple_size<T>::type> {};

template <size_t I>
using size_ = std::integral_constant<size_t, I>;

int main() {
    S<std::tuple<int>, size_<1>> s;
}

gcc 和 clang 都接受那个。