重载解析为尚不可见的函数
Overload resolution resolves to a function not visible yet
这是这个问题的后续。
#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;
}
在上面的代码中,foo(type2)
在main
中实例化bar<type2>
时不可见。然而,代码编译并生成以下输出:
foo(type1)
foo(type2)
编译器如何知道在main
中实例化bar<type2>
时foo(type2)
可用?
编辑:我正在尝试更多地了解模板实例化期间的重载解析如何工作。考虑下面的代码:
#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>()
时,即使只有foo(type2)
可见,编译器仍然会选择稍后出现的foo(type3)
因为这是更接近的匹配。
任何没有定义的符号都将在链接过程中被替换,因为该函数foo(type2)
可以在另一个文件中提供。
编译器是说,当不能应用进一步的替换时,在整个过程结束时是否已经定义了所需的函数。
为了澄清理解,您必须了解编译通用 C 程序所需的步骤:
-
首先,展开代码上的所有宏;
-
然后根据语言语法验证你的代码,以便它可以转换为汇编语言——编译过程本身; 在此步骤中,每个没有定义的符号都会在带有条目的表中进行注释
(symbol, definition)
,这将在以后完成,从而可以正确构建您的程序; -
接下来,编译成汇编的代码将被转换为机器语言,即创建对象;
-
最后,您需要链接已经可执行的对象,以解决对符号定义的任何依赖关系;最后一步检查对象中是否存在未定义的符号,从其他模块或库添加定义,从而完成程序。
如果任何符号没有正确"链接"到其定义,编译器将指出程序中的错误 - 经典undefined reference to...
。
考虑到您发布的代码,该过程将被执行,直到它到达编译器。编译器将遍历代码,注意type1
、type2
、foo(type1 x)
和bar<T>()
的定义。
struct type1 {};
struct type2 {};
当它到达主时,它会找到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;
}
因此,在下一步中,编译能够将符号替换为其各自的定义,并且程序可以正确编译。
问候!
答案是通过参数相关的名称查找(ADL)找到的(在链接的问题中也提到了)。foo(T());
有两个查找。首先在模板定义时,在定义点定义的任何函数都包含在重载集中。这意味着当编译器看到bar
内部的foo(T());
时,它只会在重载集中添加void foo(type1 x)
。但是,执行了第二个查找,称为 ADL。在模板实例化时,即bar<type2>();
,它会在与提供的参数相同的命名空间中查找foo
,在本例中为type2
。由于type2
位于全局命名空间中,因此它会查找一个foo
,该在全局命名空间中获取type2
并找到它,并解析调用。如果要查找标准中的信息,请参阅14.6.4.2 Candidate functions
。
请尝试以下操作并观察代码失败。这是因为它无法在与a::type1
相同的命名空间中找到foo
。
#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;
}
- 为什么使用SFINAE而不是函数重载
- 为什么我不能在 C++ 中的特定函数重载中调用同一函数的任何其他重载?
- c++:可变模板和函数重载
- 在缺少函数重载时抛出异常,并带有 std::variant 而不是编译时错误
- 解决模板成员函数重载
- 为什么不允许成员函数和非成员函数之间的函数重载?
- 推断模板化函数中的函数重载
- C++复制函数重载导致"must be a nonstatic member function"错误
- 为什么 std::sort 找不到合适的(静态成员)函数重载?
- 可变参数泛型 lambda 和函数重载
- C++中的函数重载和继承
- 当有右值构造函数可用时,为什么从右值调用类引用构造函数重载?
- C/C++ 可变参数宏函数重载
- 将基类的成员函数重载到其他派生类C++
- C++ 函数重载匹配
- C++函数重载,具体步骤是什么
- C++:使用 param pack 显式调用函数重载
- 隐式生成的函数重载用于右值参数?
- 使用函数重载输入运算符
- 运算符重载函数上的函数重载