在什么时候发生模板实例化绑定?

At which point occurs template Instantiation binding?

此代码来自 Bjarne Stroustrup 的 "C++ programming language"(C.13.8.3 实例化绑定点)

template <class T>
void f(T value)
{
    g(value);
}

void g(int v);

void h()
{
    extern g(double);
    f(2);
}

他提到:

Here, the point of instantiation for f() is just before h(), so the g() called in f() is the global g(int) rather than the local g(double). The definition of ‘‘instantiation point’’ implies that a template parameter can never be bound to a local name or a class member.

void h()
{
    struct X {}; // local structure
    std::vector<X> v; // error: can't use local structure as template parameter
}

我的问题是:

  1. 为什么第一个代码应该有效? g() 稍后声明,我确实收到 G++ 4.9.2 错误,即 g 未在该点声明。

  2. extern g(double) - 这是如何工作的?由于 return 值在函数重载的情况下无关紧要,那么我们可以在前向声明中忽略它吗?

  3. f() 的实例化点就在 h() 之前 - 为什么?当 f(2) 被调用时它会被实例化是不是合乎逻辑?就在我们称之为它的地方,g(double) 已经在范围内了。

  4. “实例化点”的定义意味着模板参数永远不能绑定到本地名称或 class 成员 - 这在 C++14 中有变化吗?我在使用 C++(G++ 4.9.2) 时出错,但在使用 C++14(G++ 4.9.2) 时没有出错。

"In 1985, the first edition of The C++ Programming Language was released, which became the definitive reference for the language, as there was not yet an official standard." wiki C++ History 所以它在 C++11 和 C++14 之间没有变化。我可以假设(请对此持保留态度)它在 "pre-standardization" 和标准化之间发生了变化。也许更了解 C++ 历史的人可以在这里阐明更多。

至于实际情况:


首先让我们摆脱简单的方式:

extern g(double);

这是无效的 C++。从历史上看,不幸的是 C 允许省略类型。在 C++ 中,您必须编写 extern void g(double).


接下来,让我们忽略 g(double) 重载来回答您的第一个问题:

template <class T>
void f(T value)
{
    g(value);
}

void g(int v);

int main()
{
    f(2);
}

在 C++ 中有臭名昭著的两阶段名称查找:

  • 在第一阶段,在模板定义处,所有 non-dependent names 都被解析。不这样做是一个硬错误;
  • 从属名称在第二阶段在模板实例化时解析。

规则有点复杂,但这就是它的要点。

g 依赖于模板参数 T 所以它通过了第一阶段。这意味着如果您从不实例化 f,代码编译得很好。在第二阶段 f 被实例化为 T = intg(int) 正在搜索,但未找到:

17 : error: call to function 'g' that is neither visible in the template definition nor found by argument-dependent lookup
g(value);
^
24 : note: in instantiation of function template specialization 'f<int>' requested here
f(2);
^
20 : note: 'g' should be declared prior to the call site
void g(int v);

为了让任意名称 g 顺利通过,我们有几个选择:

  1. 之前声明g
void g(int);

template <class T>
void f(T value)
{
    g(value);
}
  1. g 带入 T:
template <class T>
void f(T)
{
    T::g();
}

struct X {
   static void g();
};

int main()
{
    X x;
    f(x);
}
  1. 通过 ADL 将 g 带入 T
template <class T>
void f(T value)
{
    g(value);
}

struct X {};

void g(X);

int main()
{
    X x;
    f(x);
}

这些当然会改变程序的语义。它们旨在说明您可以在模板中拥有什么,不能拥有什么。


至于为什么ADL没有找到g(int),而是找到了g(X)

§ 3.4.2 Argument-dependent name lookup [basic.lookup.argdep]

  1. For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered [...]:

    • If T is a fundamental type, its associated sets of namespaces and classes are both empty.

    • If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces of which its associated classes are members. [...]


最后我们明白了为什么 extern void g(double); 在 main 里面找不到:首先我们证明 g(fundamental_type) 被发现 iff 它是在 [=22= 之前声明的] 定义。因此,让我们在 main 中设置 void g(X)。 ADL 找到了吗?

template <class T>
void f(T value)
{
    g(value);
}

struct X{};


int main()
{
  X x;
  void g(X);

  f(x);
}

没有。因为它不在与 X 相同的命名空间(即全局命名空间)中,所以 ADL 无法找到它。

证明 g 不在全局

int main()
{
  void g(X);

  X x;
  g(x); // OK
  ::g(x); // ERROR
}

34 : error: no member named 'g' in the global namespace; did you mean simply 'g'?