当我使用函数模板时,我注意到将其中一个函数模板的定义移动到不同的翻译单元会解析ambiguous error
。下面是我尝试过的两个例子。第一个例子产生了预期的模糊错误,但当我将其中一个函数模板的定义移动到另一个翻译单元时,错误就消失了。
示例1
#include <iostream>
template<typename X, typename Y>
void func(X, Y)
{
std::cout<<"X-Y in order version called"<<std::endl;
}
template<typename X, typename Y>
//--------v--v----->order changed
void func(Y, X)
{
std::cout<<"Y-X in order version called"<<std::endl;
}
int main()
{
func(2,2); //this is ambiguous as expected
}
演示显示我们得到了不明确的错误如预期。
我的问题是关于下面给出的第二个例子:
示例2
main.cpp
#include <iostream>
template<typename X, typename Y>
void func(X, Y)
{
std::cout<<"X-Y in order version called"<<std::endl;
}
extern void init();
int main()
{
func(2,2); //WORKS BUT HOW? How does the compiler resolves the ambiguity here
init();
}
源2.cpp
#include <iostream>
//moved to source2.cpp
template<typename X, typename Y>
//--------v--v----->order changed
void func(Y, X)
{
std::cout<<"Y-X in order version called"<<std::endl;
}
void init()
{
func(2,2);
}
上面给出的第二个版本成功地编译并产生了输出:
X-Y in order version called
Y-X in order version called
我的问题是:
当我将第二个重载的定义移动到不同的翻译单元时,如何解决歧义?我的意思是,我们仍然有两个接口(一个来自main.cpp中的重载,另一个来自source2.cpp中的重载),但现在我们没有得到模糊性错误。那么C++标准是如何解决这种歧义的呢。
C++标准如何允许选择/首选第一个过载而不是第二个。我的意思是,在标准中有没有一个参考文献说,应该选择来自同一翻译单元中重载的实例化。
摘要
请注意,我的第二个问题是为什么第一个版本比另一个翻译单元的版本更受欢迎。而我的第一个问题是,当将定义转移到另一个翻译单元时,如何消除歧义。
当我将第二个重载的定义移动到不同的翻译单元时,如何解决歧义?
您不仅将定义,而且将第二个重载的唯一声明移动到了第二个转换单元中。每个翻译单元现在只知道其中一个过载。
重载解析只将通过从函数调用的上下文中查找名称可以找到的声明视为候选声明。因此,在第一翻译单元中只有第一过载被发现为候选,而在第二翻译单元中只能找到第二过载。
因此,过载决议将只有一个可行的候选人可供选择。不可能含糊不清。
过载的解决取决于引入了哪些声明,这不是一个问题。只有在违反ODR的情况下,这才是一个问题,例如,因为同一inline
函数的定义进行的调用会导致两个转换单元中的不同过载解决方案。
我的意思是,我们仍然有两个初始化(一个来自main.cpp中的重载,另一个来自source2.cpp中的重载),但现在我们没有得到模糊性错误。
有两个实例化,但它们是不同函数模板的实例化,因此也是不同函数的实例化。这没有理由成为一个问题。为哪个函数调用哪个模板专用化已经由重载解析决定。不可能混淆他们。
在第二个例子中,它并不含糊,因为编译器看不到第二个翻译单元,因此只在隐式实例化函数后编译对void func<int,int>(int,int)
的调用在第二个翻译单元中,您也在实例化void func<int,int>(int,int)
,但使用了不同的定义,它是IFNDR(格式不正确,不需要诊断)。链接器可以选择任何定义,因此您的程序将具有未定义的行为
C++标准如何解决这种歧义。
来自临时链接#1:
1.可以重载函数模板,使两个不同的函数模板专业化具有相同的类型。
2. 这样的专门化是不同的函数,不违反一个定义规则。
(强调矿)
现在,在给定的示例中,由两个重载产生的两个专门化都将具有相同的类型void (int, int)
,正如上面几点所引用的,这种用法是允许的。
C++标准如何允许选择/首选第一个过载而不是第二个。
要回答第二个问题,在重载解析过程中,source2.cpp中函数init
内部的调用func(2,2)
已绑定到第二个重载的实例化版本。类似地,对于main.cpp内部的调用表达式func(2,2)
,它被绑定到第一个重载的实例化版本。因此,当从main.cpp
内部调用init
时,调用第二版本。如果我们更改main.cpp内部调用的顺序,那么输出将被反转,因为在重载解析过程中,调用已经绑定到它们各自的版本。