重载分辨率解析为尚未显示的功能

kev*_*man 5 c++

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

foo(type1)
foo(type2)
Run Code Online (Sandbox Code Playgroud)

如何编译器知道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;
}
Run Code Online (Sandbox Code Playgroud)

输出是

foo(type2)
Run Code Online (Sandbox Code Playgroud)

即使可以进行更接近的匹配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;
}
Run Code Online (Sandbox Code Playgroud)

输出是

foo(type3)
Run Code Online (Sandbox Code Playgroud)

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

Rub*_*ens 6

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

编译器将说明是否已经在整个过程结束时定义了所需的功能,此时不能应用进一步的替换.

为了澄清理解,您必须了解编译常见C程序所需的步骤:

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

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

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

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

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

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

struct type1 {};
struct type2 {};
Run Code Online (Sandbox Code Playgroud)

当它到达主要时,它会找到已经知道的呼叫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;

}
Run Code Online (Sandbox Code Playgroud)

一旦它到达下一个调用,bar<type2>();它将尝试调用foo(type2()),但是没有这样的定义可用于使用,因此它将此调用与未知符号相关联,必须由后续进程中的定义替换.

在编译器运行之后main,它会到达一个新的定义,这正是在创建的"转换表"上缺少定义的定义.

void foo(type2 x) {
    std::cout << "foo(type2)" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

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

问候!


Jes*_*ood 3

答案是通过参数相关的名称查找(ADL)找到的(在链接的问题中也提到了)。foo(T());有两个查找。首先,在模板定义时,在定义点定义的任何函数都包含在重载集中。这意味着当编译器看到 的foo(T());内部时bar,它 void foo(type1 x)添加到重载集中。但是,还会执行第二次查找,称为 ADL。在模板实例化时,即它在与提供的参数相同的命名空间中bar<type2>();查找 a ,在本例中为。由于is 在全局命名空间中,因此它会在全局命名空间中查找接受 a 的a并找到它,并解析调用。如果您正在寻找标准中的信息,请参阅。footype2type2footype214.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;
}
Run Code Online (Sandbox Code Playgroud)