这是对这个问题的一种跟进.
#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)稍后出现,因为这是一个更接近的匹配.
在链接过程中,任何没有定义的符号都将被替换,因为该函数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)
因此,在下一步中,编译能够用其各自的定义替换符号,并且程序正确编译.
问候!
答案是通过参数相关的名称查找(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)