39

考虑以下代码段:

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

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

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

哪个编译器是正确的?

4

3 回答 3

23

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

在第 14.6 节 (p10) 中,如果名称不依赖于模板参数,则在声明点绑定名称。如果它依赖于模板参数,则在第 14.6.2 节中定义。

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

现在因为调用f()取决于它的参数。您查看参数类型以查看它是否取决于类型。参数为False<T>::value ? d : d。这里的第一个条件取决于 type 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]

除非下面描述,如果任何子表达式是类型相关的,则表达式是类型相关的

于 2016-06-01T09:23:17.570 回答
7

我认为 gcc(顺便说一下,还有 Visual Studio)是正确的。

n4582,§14.6.2.2

除非下面描述,如果任何子表达式是类型相关的,则表达式是类型相关的。

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

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

由于有一个类型相关的子表达式,并且三元运算符没有出现在 §14.6.2.2 的例外列表中,因此它被认为是类型相关的。

于 2016-06-01T09:23:49.770 回答
1

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

除非函数模板特化已被显式实例化或显式特化,否则当在需要函数定义存在的上下文中引用特化时,函数模板特化将被隐式实例化。除非调用函数模板显式特化或显式特化类模板的成员函数,否则函数模板的默认参数或类模板的成员函数在需要的上下文中调用函数时会隐式实例化默认参数的值。

我会说 gcc 对此更正确。

例如,如果你创建一个专门的版本,void g()你会让两个编译器都做同样的事情

于 2016-06-01T09:22:45.857 回答