条件运算符的返回类型和两阶段查找

时间:2016-06-01 08:48:05

标签: c++ language-lawyer compiler-bug name-lookup dependent-name

请考虑以下代码段:

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 // 1的函数调用,因为它的参数类型是明确的Derived&,并且因此被解析为f(Base&),这是范围内唯一的。{/ p>

Clang 3.8.0 agrees with me,但GCC 6.1.0 doesn't,并将f的查询推迟至第二阶段,其中f(Derived&)被选中。

哪种编译器是对的?

3 个答案:

答案 0 :(得分:23)

使用最新版本的C++ standard目前n4582

在第14.6节(第10页)中,如果名称不依赖于模板参数,则表示名称在声明点处被绑定。如果它取决于模板参数,则在第14.6.2节中定义。

第14.6.2.2节接着说,如果任何子表达式是类型相关的,则表达式是类型相关的。

现在,因为对f()的调用取决于其参数。您查看参数类型以查看它是否取决于类型。参数为False<T>::value ? d : d。这里第一个条件取决于类型T

因此我们得出结论,调用是在实例化而非声明的情况下绑定的。因此应绑定到:void f(Derived &) { std::cout << "f(Derived&)\n"; }

因此g ++具有更准确的实现。

  

14.6名称解析[temp.res]

     

第10段:

     

如果名称不依赖于模板参数(如14.6.2中所定义),则该名称的声明(或声明集)应在范围内。名称出现在模板定义中;该名称绑定到该点发现的声明(或声明),并且此绑定不受在实例化时可见的声明的影响。

     

14.6.2.2依赖于类型的表达式[temp.dep.expr]

     

除非如下所述,否则如果任何子表达式取决于类型,则表达式依赖于类型。

答案 1 :(得分:7)

我认为gcc(顺便提一下,视觉工作室)就是这个。

  

n4582,§14.6.2.2

     

除非如下所述,否则如果任何子表达式依赖于类型,则表达式依赖于类型。

T{} ? d : d中,有3个子表达式:

  • T{},显然是类型依赖
  • d(2次),不依赖于类型

由于存在类型相关的子表达式,并且三元运算符未在§14.6.2.2的异常列表中进行计算,因此它被视为类型相关。

答案 2 :(得分:1)

根据c ++草案(n4582)§14.7.1.5:

  

除非已明确表示功能模板专业化   实例化或明确专门化的函数模板   专业化是在专业化时隐式实例化的   在需要函数定义存在的上下文中引用。   除非调用函数模板显式特化或者   一个显式专用类模板的成员函数,a   函数模板的默认参数或者函数的成员函数   调用函数时隐式实例化类模板   在需要默认参数值的上下文中。

我想说gcc对此更为正确。

例如,如果您创建void g()的专用版本,则会获得both compiler doing the same