identity
模板是关闭某个(依赖)模板参数的演绎的常用习惯用法,例如允许隐式转换,如下例所示:
#include <iostream>
template<class T>
struct A {};
struct B : public A<int> {};
struct C {
operator B() { return {}; }
};
template<typename U> struct identity { typedef U type; };
template<class T> using identity_t = typename identity<T>::type;
template<class X>
void test(A<X> arg1, A<X> arg2) { // #1
std::cout << "ok1";
}
template<class X>
void test(A<X> arg1, identity_t<A<X>> arg2) { // #2
std::cout << "ok2";
}
int main() {
B a, b;
C c;
test(a, b);
test(b, c);
}
但不同编译器的结果并不相同:Live demo on Godbolt
ok1ok2
ok1ok2
27 : <source>(27): error C2668: 'test': ambiguous call to overloaded function 20 : <source>(20): note: could be 'void test<int>(A<int>,A<int>)' 15 : <source>(15): note: or 'void test<int>(A<int>,A<int>)' 27 : <source>(27): note: while trying to match the argument list '(B, B)'
错误类型有意义(虽然肯定是MSVC中的一个错误),因此带给我们关于为什么以及它如何在GCC和clang中起作用的问题:
test(a, b)
- #1
时test(b, c)
如何选择#2
?至少在test(a, b)
{。}}
为什么编译器抱怨两个test
实例具有完全相同的签名?
答案 0 :(得分:1)
这是MSVC中的一个错误,程序是正确的。
test(a, b)
如何选择#1
?
对于test(a, b)
,重载决策从函数调用中执行参数推导(参见[temp.deduct.call]):
void test(A<int>, A<int>)
void test(A<int>, <non-deduced context>)
,然后arg2从arg1合成为A<int>
,结果为:void test(A<int>, A<int>)
有多个可行的替代方案,因此该过程将继续部分排序(请参阅[temp.deduct.partial])。
部分排序使用原始模板,尝试从类型([temp.deduct.type])中的扣除,将一个模板的每个参数成对地转换为另一个模板(之后)小转换),反之亦然。 如果扣除仅在一个方向上成功,则选择获胜模板作为最专业的。
在嵌套的上下文中,类型的扣除总是失败(范围操作符::
左侧的任何内容都是嵌套的上下文),请参阅[temp.deduct.type]/5:
未推断的上下文是:
- 使用 nested-name-specifier 指定的类型的 qualified-id 。
。 。
所以这意味着#2总会在部分排序中丢失;对它的推论总是会失败,而另一种方式总是会成功:
从void test(A<T>,A<T>)
推导void test(A<U>, typename identity<A<U>>::type)
:P1 = A<T>
,A1 = A<U>
,P2 = A<U>
,A2 = A<U>
,成功,T = U
从void test(A<T>, typename identity<A<T>>::type)
推导void test(A<U>,A<U>)
:P1 = A<T>
,A1 = A<U>
,P2 = <non-deduced-context>
,失败
因此,部分排序的结果是:使用void test(A<T>,A<T>)
(#1)进行调用test(a, b)
。
test(b, c)
如何选择#2
?
对于test(b, c)
,无法从A<X>
推断出C
(在演绎时不考虑隐式转换),因此#2是唯一可行的替代方案。在扣除identity_t<A<X>>
之后解析,因为A<int>
已知(从第一个参数推断出来)。
为什么编译器不抱怨具有完全相同签名的两个测试实例?
函数声明中引用的模板参数是实例化函数的签名的一部分。见[temp.over.link]:
可以重载函数模板,以便两个不同的函数模板特化具有相同的类型。
- 醇>
此类专业化是不同的功能,不违反单一定义规则。