功能模板的部分排序和非推导上下文在MSVC 2017中不起作用 [英] Partial ordering of function templates and non-deduced context not working in MSVC 2017
问题描述
identity
模板是一种常见的惯用法,用于关闭某些(从属)模板参数的推导,例如,允许隐式转换,如下面的示例所示:
The identity
template is a common idiom for switching off deduction for a certain (dependent) template argument, for example to allow for implicit conversions like in the example below:
#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);
}
但是来自不同编译器的结果并不相同: Godbolt上的实时演示
But the results from different compilers are not the same: Live demo on Godbolt
- GCC 6:
ok1ok2
- c 5:
ok1ok2
- MSVC 2017:
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中的工作方式:
The error kind of makes sense (although is surely a bug in MSVC) and thus brings us to my questions about why and how it works in GCC and clang:
-
如何
test(a,b)
是否选择#1
而test(b,c)
-#2
?至少在test(a,b)
的情况下,它们看起来像是同样好的候选人。
How does
test(a, b)
select#1
whiletest(b, c)
-#2
? They look like equally good candidates at least in casetest(a, b)
.
为什么没有编译器不是抱怨两个完全相同签名的 test
实例吗?
Why isn't the compiler complaining about two test
instantiations with an exact same signature?
推荐答案
这是MSVC中的错误,程序是正确的。
It's a bug in MSVC, the program is correct.
test(a,b)
如何选择#1
?
对于 test(a,b)
,重载解析从函数调用(请参见 [temp.deduct.call] ):
For test(a, b)
, overload resolution performs argument deduction from a function call (see [temp.deduct.call]):
- #1推导为
void test(A< int< A< int>))
- #2被推导出为
void test(A< int> ;,< non-deduced context>)
,然后从arg1中将arg2合成为A
,结果:void test(A
,A&l t; int>)
- #1 is deduced as
void test(A<int>, A<int>)
- #2 is deduced as
void test(A<int>, <non-deduced context>)
, arg2 is then synthesized from arg1 asA<int>
, the result:void test(A<int>, A<int>)
有不止一种可行的选择,因此该过程以部分订购(请参见 [temp.deduct.partial] )。
There is more than one viable alternative, so the process continues with partial ordering (see [temp.deduct.partial]).
部分排序使用原始模板,尝试从类型中扣除 (< a href = https://timsong-cpp.github.io/cppwp/n3337/temp.deduct.type rel = nofollow noreferrer> [temp.deduct.type] )将一个模板的参数转换为另一个模板(经过较小的转换后),反之亦然。
如果扣除仅在一个方向上成功,则获胜模板将被选为最专业的。
Partial ordering uses the original templates, trying a deduction from a type ([temp.deduct.type]) pair-wise of each argument of one template into another (after a minor conversion), and vice versa. If the deduction succeeds in only one direction, the winning template is selected as the most specialized.
从类型的扣除总是失败在嵌套上下文中(范围运算符 ::
左侧的任何内容都是嵌套上下文),请参见 [temp.deduct.type] / 5 :
Deduction from a type always fails at nested contexts (anything to the left of the scope operator ::
is a nested context), see [temp.deduct.type]/5:
非推论上下文为:
The non-deduced contexts are:
—
— The nested-name-specifier of a type that was specified using a qualified-id.
。 。 。
因此,这意味着#2总是会丢失部分排序;推论总是失败的,反之则总是会成功的:
So this means that #2 will always lose in partial ordering; the deduction into it will always fail, whereas the other way will always succeed:
-
推论
void测试(A T,A T)
来自void测试(A U,类型名称标识A A U:>:类型)
:P1 =A< T>
,A1 =A< U>
,P2 =A
,A2 =A
,成功,T =U
Deducing
void test(A<T>,A<T>)
fromvoid test(A<U>, typename identity<A<U>>::type)
: P1=A<T>
, A1=A<U>
, P2=A<U>
, A2=A<U>
, success, T=U
推导 void测试(A T,类型名称标识A T :: type)来自
void测试(A ,A )
的code>:P1 = A
,A1 = A
,P2 =
,失败
Deducing void test(A<T>, typename identity<A<T>>::type)
from void test(A<U>,A<U>)
: P1=A<T>
, A1=A<U>
, P2=<non-deduced-context>
, fail
因此,部分排序的结果是:使用 void test(A< T>,A< T>)
(#1)进行通话 test(a,b)
。
So the outcome of partial ordering is: use void test(A<T>,A<T>)
(#1) for the call test(a, b)
.
test(b,c)
如何选择#2
?
用于 test(b ,c)
, A< X>
不能从 C
推导(隐式转换为因此,#2是唯一可行的替代方案。 identity_t< A< X>>
在扣除到 A< int<
,因为已知 X
(根据第一个参数推导)。
For test(b, c)
, A<X>
cannot be deduced from C
(implicit conversions are not considered during deduction), so #2 is the only viable alternative. identity_t<A<X>>
is resolved after deduction to A<int>
, since X
is known (deduced from the first argument).
为什么编译器不抱怨具有完全相同签名的两个测试实例?
Why isn't the compiler complaining about two test instantiations with an exact same signature?
函数声明中引用的模板参数是实例化函数签名的一部分。参见 [temp.over.link] :
The template parameters referenced in the function declaration are part of the signature of the instantiated function. See [temp.over.link]:
-
可以重载函数模板,以便两个不同的函数模板专长具有相同的类型。
It is possible to overload function templates so that two different function template specializations have the same type.
此类专业化是不同的功能,并且不违反一定义规则。
Such specializations are distinct functions and do not violate the one-definition rule.
这篇关于功能模板的部分排序和非推导上下文在MSVC 2017中不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!