5

这是对这个问题的一种跟进。

#include <iostream>

struct type1 {};
struct type2 {};

void foo(type1 x)
{
  std::cout << "foo(type1)" << std::endl;
}

template<typename T>
void bar() {
  foo(T());
}

int main()
{
  bar<type1>();
  bar<type2>();
  return 0;
}

void foo(type2 x)
{
  std::cout << "foo(type2)" << std::endl;
}

上面的代码在infoo(type2)的实例化时是不可见的。然而代码编译并产生以下输出:bar<type2>main

foo(type1)
foo(type2)

编译器如何知道foo(type2)在实例化时bar<type2>可用main

编辑:我试图更多地了解模板实例化期间的重载解析是如何工作的。考虑下面的代码:

#include <iostream>

struct type1 {};
struct type2 {};
struct type3 {
  operator type2() { return type2(); }
};

void foo(type1 x)
{
  std::cout << "foo(type1)" << std::endl;
}

void foo(type2 x)
{
  std::cout << "foo(type2)" << std::endl;
}

int main()
{
  foo(type3());
  return 0;
}

void foo(type3 x)
{
  std::cout << "foo(type3)" << std::endl;
}

输出是

foo(type2)

即使有更接近的匹配foo(type3)可用,调用也会foo(type3())解析为,foo(type2)因为这是编译器在此之前解析过的唯一候选对象。现在考虑以下代码:

#include <iostream>

struct type1 {};
struct type2 {};
struct type3 {
  operator type2() { return type2(); }
};

void foo(type2 x)
{
  std::cout << "foo(type2)" << std::endl;
}

template<typename T>
void bar() {
  foo(T());
}

int main()
{
  bar<type3>();
  return 0;
}

void foo(type3 x)
{
  std::cout << "foo(type3)" << std::endl;
}

输出是

foo(type3)

也就是说,在调用点bar<type3>(),即使 onlyfoo(type2)是可见的,编译器仍然会选择foo(type3)稍后出现的,因为这是更接近的匹配。

4

2 回答 2

6

任何没有定义的符号都将在链接过程中被替换,因为该函数foo(type2)可以在另一个文件中提供。

编译器是说在整个过程结束时是否已经定义了需要的函数,此时不能再进行替换。

为了澄清理解,您必须了解编译(例如,一个普通的 C 程序)所需的步骤:

  • 首先,展开代码中的所有宏;

  • 然后根据语言语法验证您的代码,以便将其转换为汇编语言——编译过程本身;在此步骤中,找到的每个没有定义的符号都将在一个带有条目的表中进行注释,这些条目(symbol, definition)将在以后完成,从而使您的程序能够正确构建;

  • 接下来,您编译成汇编的代码将被转换为机器语言,即创建对象;

  • 最后,您需要链接您已经可执行的对象,以解决对符号定义的任何依赖;最后一步检查您的对象是否有未定义的符号,添加来自其他模块或库的定义,从而完成程序。

如果任何符号未正确“链接”到其定义,编译器将指出您程序中的错误——经典的undefined reference to....

考虑到您发布的代码,该过程将一直执行,直到它到达编译器。编译器会遍历代码,注意type1type2foo(type1 x)和的定义bar<T>()

struct type1 {};
struct type2 {};

当它到达 main 时,它会找到对 的调用bar<type1>();,并且会调用foo(type1()),这是已知的,可以正确使用。

void foo(type1 x) {
    std::cout << "foo(type1)" << std::endl;
}

template<typename T>
void bar() {
    foo(T());
}

int main() {

    bar<type1>();
    bar<type2>();
    return 0;

}

一旦它到达下一次调用,bar<type2>();它会尝试调用foo(type2()),但是没有这样的定义可供使用,所以它会将这个调用作为一个未知符号关联起来,在以后的过程中必须用一个定义替换它。

在编译器运行完之后main,它会得到一个新的定义,这正是正在创建的“翻译表”上缺少定义的那个。

void foo(type2 x) {
    std::cout << "foo(type2)" << std::endl;
}

因此,在下一步中,编译能够将符号替换为其各自的定义,并且程序可以正确编译。

问候!

于 2012-11-21T01:50:13.690 回答
3

答案是通过与参数相关的名称查找 (ADL) 找到的(在链接的问题中也提到了这一点)。foo(T());有两个查找。首先在模板定义时,在定义点定义的任何函数都包含在重载集中。这意味着当编译器看到foo(T());内部时,它只bar添加到重载集。但是,会执行第二次查找,称为 ADL。在模板实例化时,即它在与提供的参数相同的命名空间中查找 a ,在本例中为. 由于是在全局命名空间中,它会寻找一个需要一个 void foo(type1 x)bar<type2>();footype2type2footype2在全局命名空间中找到它,然后解析调用。如果您正在寻找来自标准的信息,请参阅14.6.4.2 Candidate functions

尝试以下操作并观察代码失败。这是因为它无法foo在与a::type1.

#include <iostream>

namespace a
{
  struct type1 {};
}

template<typename T>
void bar() {
  foo(T());
}

int main()
{
  bar<a::type1>();
  return 0;
}

void foo(a::type1 x)
{
  std::cout << "foo(a::type1)" << std::endl;
}
于 2012-11-21T03:22:55.937 回答