在哪个位置发生模板实例化绑定?

use*_*289 21 c++ templates name-binding c++14 template-instantiation

此代码来自Bjarne Stroustrup的"C++编程语言"(C.13.8.3实例化绑定点)

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

void g(int v);

void h()
{
    extern g(double);
    f(2);
}
Run Code Online (Sandbox Code Playgroud)

他提到:

这里,f()的实例化点恰好在h()之前,因此在f()中调用的g()是全局g(int)而不是局部g(double)."实例化点"的定义意味着模板参数永远不能绑定到本地名称或类成员.

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

我的问题是:

  1. 为什么第一个代码有效?g()稍后声明,我真的得到了G ++ 4.9.2的错误,那时g没有声明.

  2. extern g(double) - 这是如何工作的?因为在函数重载的情况下返回值无关紧要,那么我们可以在前向声明中错过它吗?

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

  4. "实例化点"的定义意味着模板参数永远不能绑定到本地名称或类成员 - 在C++ 14中是否已更改?我在使用C++(G ++ 4.9.2)时遇到错误,但是在C++ 14(G ++ 4.9.2)中没有出错.

bol*_*lov 15

"1985年,第一版的C++编程语言发布,成为该语言的权威参考,因为还没有官方标准." wiki C++历史因此它在C++ 11和C++ 14之间没有变化.我可以假设(并且请带着一点点盐)它在"预标准化"和标准化之间发生了变化.也许更了解C++历史的人可以在这里获得更多的亮点.

至于实际发生的事情:


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

extern g(double);
Run Code Online (Sandbox Code Playgroud)

这是无效的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);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 在第一阶段,在模板定义中,解析所有非依赖名称.不这样做是一个很难的错误;
  • 在模板实例化时,在第二阶段解析从属名称.

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

g依赖于模板参数,T因此它通过了第一阶段.这意味着如果你从未实例化f,代码编译就好了.在第二阶段f实例化T = int.g(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);
Run Code Online (Sandbox Code Playgroud)

为了让任意名称g通过飞扬的颜色,我们有几个选择:

  1. g先前声明:
void g(int);

template <class T>
void f(T value)
{
    g(value);
}
Run Code Online (Sandbox Code Playgroud)
  1. gT:
template <class T>
void f(T)
{
    T::g();
}

struct X {
   static void g();
};

int main()
{
    X x;
    f(x);
}
Run Code Online (Sandbox Code Playgroud)
  1. gT通过ADL:
template <class T>
void f(T value)
{
    g(value);
}

struct X {};

void g(X);

int main()
{
    X x;
    f(x);
}
Run Code Online (Sandbox Code Playgroud)

这些当然会改变程序的语义.它们旨在说明您在模板中可以和不可以拥有的内容.


至于ADL为什么没找到g(int),却发现g(X):

§3.4.2依赖于参数的名称查找[basic.lookup.argdep]

  1. 对于函数调用中的每个参数类型T,有一组零个或多个关联的命名空间以及一组零个或多个关联的类要考虑[...]:

    • 如果T是基本类型,则其关联的命名空间和类集都是空的.

    • 如果T是类类型(包括联合),则其关联的类是:类本身; 它所属的成员,如果有的话; 及其直接和间接基类.其关联的名称空间是其关联类是成员的名称空间.[...]


最后我们得到了,为什么extern void g(double);里面主要是没有发现:首先我们发现,g(fundamental_type)发现当且仅当它之前宣布f的定义.所以让我们把它void g(X)放进去main.ADL找到了吗?

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

struct X{};


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

  f(x);
}
Run Code Online (Sandbox Code Playgroud)

不会.因为它不与X(即全局命名空间)位于同一名称空间中,所以ADL无法找到它.

证明g不在全球范围内

int main()
{
  void g(X);

  X x;
  g(x); // OK
  ::g(x); // ERROR
}
Run Code Online (Sandbox Code Playgroud)

34:错误:全局命名空间中没有名为'g'的成员; 你的意思是'g'吗?