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

Conditional operator's return type and two-phase lookup

本文关键字:查找 段查找 返回类型 条件运算符      更新时间:2023-10-16

考虑以下片段:

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();
}

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

Clang 3.8.0同意我的观点,但GCC 6.1.0不同意,并将f的查找推迟到第二阶段,即f(Derived&)的查找。

哪个编译器是对的?

使用最新版本的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]

除以下所述外,如果任何子表达式都是类型依赖的,则表达式是类型依赖性的。

我认为gcc(顺便说一句,还有visualstudio)在这方面做得很好。

n4582,§14.6.2.2

除以下描述外,如果任何子表达式都是类型依赖的,则表达式是类型依赖性的。

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

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

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

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

除非函数模板专门化已明确实例化或显式专用的函数模板当特殊化为在需要函数定义存在的上下文中引用。除非调用是对函数模板显式专门化或显式专用类模板的成员函数函数模板或的成员函数的默认参数类模板在调用函数时被隐式实例化在需要默认参数值的上下文中。

我认为gcc对此更正确。

例如,如果您创建void g()的专用版本,那么两个编译器都会执行相同的操作。