我试图了解过载选择规则如何导致以下(非直观)行为.当我有以下功能时:
#include <iostream>
// Overload 1
template<class T>
void write(T data)
{
std::cout << "Called write(T data)" << std::endl;
}
// Overload 2
template<class T, class ...U>
void write(T&& obj, U&&... objs)
{
std::cout << "Called write(T&& obj, U&&... objs)" << std::endl;
}
int main(int, char**)
{
int j = 0;
write(j);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
选择void write(T data)过载(过载1).我认为这对我有意义:过载选择的候选者是void write<T>(T) T = int和void write<T,U>(T&) T = int, U = <>.都write(T)和write(T&)将同样的专门的,但这样过载1被选择过载2有一个空的参数包.但是,如果我添加第三个重载:
#include <iostream>
// Overload 0
void write(const int& data)
{
std::cout << "Called write(const int& data)" << std::endl;
}
// Overload 1
template<class T>
void write(T data)
{
std::cout << "Called write(T data)" << std::endl;
}
// Overload 2
template<class T, class ...U>
void write(T&& obj, U&&... objs)
{
std::cout << "Called write(T&& obj, U&&... objs)" << std::endl;
}
int main(int, char**)
{
int j = 0;
write(j);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
然后突然void write(T&& obj, U&&... objs)(Overload 2)被调用.为什么添加一个未被选中的重载会改变实际选择哪个重载?
如果唯一的候选人是,void write<T,U>(T&) T = int, U = <>并且void write(const int&)我理解为什么void write<T,U>(T&)会被选中,那么添加额外的过载可能会阻止void write(T data)参与过载选择?如果是这样的话?
由于这似乎是编译器特定的行为,因此在gcc 7.3.0上观察到了这一点.
一些更有趣的行为:如果函数被重新排序,使得新的重载放在原始的两个之间(即Overload 1,然后Overload 0,然后Overload 2),那么gcc会拒绝它call of overloaded ‘write(int&)’ is ambiguous.如果重新排序函数,使得新的重载最后(即过载1,然后过载2,然后过载0)则write(const int& data)选择.
我认为这是一个 GCC 错误:
重载是:
write(const int&)write(T) [T=int] -> write(int)write(T&&,U&&...) [T=int&,U=[]] -> write(int&)重载 0 比重载 1 更匹配,因为重载 0 不是模板函数特化。
重载 1 比重载 2 更匹配,因为重载 1 是比重载 2 更专业的函数模板。
重载 2 比重载 0 匹配得更好,因为重载 2 的参数类型的 cv 限定符int&是重载 0: 的参数类型的子集const int&。
因此,正如 Clang 所报告的那样,该调用是不明确的。
为了简化,在比较两个函数时,最好的可行函数分 4 个步骤进行评估: