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

Que*_*tin 39 c++ dependent-name language-lawyer compiler-bug name-lookup

请考虑以下代码段:

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();
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我认为应该在第一阶段查找对fat 的函数调用// 1,因为它的参数的类型是明确的Derived&,因此可以解析为f(Base&)范围中唯一的一个.

Clang 3.8.0同意我的观点,但是GCC 6.1.0没有,并推迟f到第二阶段的查找,在那里f(Derived&)被选中.

哪个编译器是对的?

Mar*_*ork 23

使用最新版本的C++标准目前为n4582.

在第14.6节(p10)中,如果名称不依赖于模板参数,则表示名称在声明点处绑定.如果它取决于模板参数,则在第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]

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


Syn*_*xis 7

我认为gcc(和视觉工作室,顺便说一句)是对的.

n4582,§14.6.2.2

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

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

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

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

  • 在处理两阶段查找时,没有太多关注MSVC - 它根本没有实现它. (4认同)