条件运算符的返回types和两阶段查找
考虑下面的代码片段:
struct Base { }; struct Derived : Base { }; void f(Base &) { std::cout << "f(Base&)\n"; } template <class T = int> void g() { Derived d; f(T{} ? d : d); // 1 } void f(Derived &) { std::cout << "f(Derived&)\n"; } int main() { g(); }
在这种情况下,我认为应该在第一阶段调用f
的函数,因为它的参数types是不可信的Derived&
,因此被parsing为f(Base&)
,它是唯一的范围。
铿锵3.8.0与我同意 ,但GCC 6.1.0并没有 ,并推迟了f
的查找,直到第二阶段,其中f(Derived&)
被拿起。
哪个编译器是正确的?
使用最新版本的C ++标准目前n4582 。
在第14.6节(p10)中,如果名称不依赖于模板参数,则表示名称被绑定在声明点上。 如果依赖于模板参数,则在14.6.2节中定义。
如果有任何子expression式是依赖于types的,那么14.6.2.2节继续说一个expression式是依赖于types的。
现在由于调用f()
是依赖于它的参数。 你看看参数types是否取决于types。 参数是False<T>::value ? d : d
False<T>::value ? d : d
。 这里第一个条件取决于typesT
因此我们得出这样的结论:调用被绑定在实例化而非声明的地方。 因此应该绑定到: void f(Derived &) { std::cout << "f(Derived&)\n"; }
void f(Derived &) { std::cout << "f(Derived&)\n"; }
因此g ++有更准确的实现。
14.6名称parsing[temp.res]
第10段:
如果名称不依赖于模板参数(如14.6.2所定义的) ,则该名称的声明(或一组声明)应该在模板定义中出现的名称的范围内; 该名称被绑定到在该点发现的声明(或声明),并且该绑定不受在实例化处可见的声明的影响。
14.6.2.2依赖于types的expression式[temp.dep.expr]
除了下面所描述的,如果任何子expression式是依赖于types的,则expression式是依赖于types的 。
我认为海湾合作委员会(和视觉工作室,顺便说一下)是正确的在这一个。
n4582 ,§14.6.2.2
除了下面所描述的,如果任何子expression式是依赖于types的,则expression式是依赖于types的。
在T{} ? d : d
T{} ? d : d
,有3个子expression式:
-
T{}
,显然是依赖于types的 -
d
(2次),不依赖于types
由于存在依赖于types的子expression式,并且三元运算符不在第14.6.2.2节中的例外列表中,所以它被认为是与types相关的。
根据C ++草案( n4582 )§14.7.1.5:
除非函数模板专门化已经被显式实例化或显式专门化,否则当在需要函数定义存在的上下文中引用专门化时,函数模板专门化被隐含地实例化。 除非调用函数模板显式特化或显式专用类模板的成员函数,否则当函数在需要上下文的上下文中调用时,函数模板或类模板的成员函数的默认参数会隐式地实例化默认参数的值。
我会说海湾合作委员会是更正确的。
如果你例如创build一个void g()
的专门版本,你可以使两个编译器做同样的事情 。