提问者:小点点

类模板特化部分排序和功能合成


选择首选哪个类模板特化的规则包括将特化重写为函数模板,并通过函数模板的排序规则[temp. class.order]确定哪个函数模板更特化。然后考虑这个例子:

#include <iostream>

template <class T> struct voider { using type = void; };
template <class T> using void_t = typename voider<T>::type;

template <class T, class U> struct A { };

template <class T> int foo(A<T, void_t<T>> ) { return 1; }
template <class T> int foo(A<T*, void> )     { return 2; }

int main() {
    std::cout << foo(A<int*, void>{});
}

gcc和clang都在这里打印2。这在前面的一些例子中是有意义的——针对非推导上下文进行推导(void针对void_t

现在考虑这个概括:

#include <iostream>

template <class T> struct voider { using type = void; };
template <class T> using void_t = typename voider<T>::type;

template <int I> struct int_ { static constexpr int value = I; };

template <class T, class U> struct A      : int_<0> { };
template <class T> struct A<T, void_t<T>> : int_<1> { };
template <class T> struct A<T*, void>     : int_<2> { };

int main() {
    std::cout << A<int*, void>::value << '\n';
}

clang和gcc都报告这种特化是不明确的,在12之间。但是为什么?合成的函数模板没有歧义。这两种情况有什么区别?


共1个答案

匿名用户

Clang是GCC兼容的(并且与依赖于这两种行为的现有代码兼容)。

考虑[temt.扣除. type]p1:

[…]尝试查找模板参数值(类型参数的类型、非类型参数的值或模板参数的模板),这将使P在替换推导值(称为推导值A)后与A兼容。

问题的关键是“兼容”在这里意味着什么。

当部分排序函数模板时,Clang仅在两个方向上推导;如果推导在一个方向上成功但在另一个方向上不成功,它假设这意味着结果将是“兼容的”,并将其用作排序结果。

然而,当类模板部分特别化时,Clang将“兼容”解释为“相同”。因此,如果将其中一个的推导参数替换为另一个将再现原始的局部特化,它只认为一个局部特化比另一个更专业。

更改这两个中的任何一个以匹配另一个会破坏大量的真实代码。:(