【问题标题】:At which point occurs template Instantiation binding?在什么时候发生模板实例化绑定?
【发布时间】:2017-02-18 22:07:55
【问题描述】:

此代码来自 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);
}

他提到:

这里,f() 的实例化点就在 h() 之前,所以 f() 中调用的 g() 是全局 g(int) 而不是局部 g(双)。 “实例化点”的定义意味着一个 模板参数永远不能绑定到本地名称或类 会员。

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) - 这是如何工作的?因为在函数重载的情况下返回值无关紧要,那么我们可以在前向声明中忽略它吗?

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

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

【问题讨论】:

  • “1985 年,C++ 编程语言的第一版发布,成为该语言的权威参考,因为还没有官方标准。” wiki 所以C++11 和C++14 之间没有变化。它在“预标准化”和标准化之间发生了变化。
  • 检查 14.6.4.1 [temp.point] 的规则
  • 同时搜索两个阶段名称查找
  • @bolov 我正在阅读第三版(1997 年)。

标签: c++ templates c++14 name-binding template-instantiation


【解决方案1】:

“1985 年,第一版 C++ 编程语言发布,成为该语言的权威参考,因为还没有官方标准。” wiki C++ History 所以它在 C++11 和 C++14 之间没有变化。我可以假设(请谨慎对待)它在“预标准化”和标准化之间发生了变化。也许更了解 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 = 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);

为了让任意名称 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 参数相关名称查找 [basic.lookup.argdep]

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

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

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


最后我们了解为什么在 main 中找不到 extern void g(double);:首先,我们证明了 g(fundamental_type) 被发现 iff 它是在 f 定义之前声明的。所以让我们在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 : 错误:全局命名空间中没有名为“g”的成员;你的意思是 只是'g'?

【讨论】:

  • 感谢您的回答。我有两个问题。 1. 为什么在第一个例子中g 是非依赖的?它依赖于 T(在本例中为 int),使用 ADL 可以在定义的命名空间 T 中找到它(据我所知,内置类型命名空间应该是全局命名空间)。 2.按照你的逻辑,为什么f(x)可以工作,而f(2)不能用ADL?
  • @user1289: f(2) 不适用于 ADL,因为 2 是 int,这是一个基本类型,并且“1) 对于基本类型的参数,关联的命名空间集并且类是空的”(参见 3.9.1 基本类型 [basic.fundamental])
  • @AndyG “依赖名称的绑定是通过查看依赖调用的参数的名称空间中的名称来完成的(全局函数在内置类型的名称空间中被考虑” - 这是来自同一本书。
  • @user1289:但是int 甚至没有引入全局函数。请参阅 bolov 在模板前声明 g 的部分。另外,我认为你应该读一本更新的书。您正在使用一本在 C++03 标准之前出版的书问 C++14 问题。
  • 我没有在文档中找到“对于基本类型的参数,相关的命名空间和类集是空的”。虽然,正如你所提到的,f(2) 不能为我编译,而 f(x) 可以编译。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 2018-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多