将函数模板定义移动到不同的翻译单元解决了歧义错误

Moving the function templates definition to different translation unit resolves the ambiguity error

我在使用函数模板时注意到将函数模板之一的定义移动到不同的翻译单元可以解析 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
    
}

Demo 表明我们得到了预期的不明确错误。

我的问题是关于下面给出的第二个例子:

示例 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();
}

source2.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);
}

上面给出的第二个版本成功编译并生成 output:

X-Y in order version called
Y-X in order version called

我的问题是:

  1. 当我将第二个重载的定义移动到不同的翻译单元时,如何解决歧义?我的意思是我们仍然有两个实例化(一个来自 main.cpp 中的重载,另一个来自 source2.cpp 中的重载),但现在我们没有得到歧义错误。那么C++标准是如何解决这种歧义的。

  2. C++ 标准如何允许第一个重载为 selected/preferred 而不是第二个。我的意思是,标准中是否有参考文献表明应选择同一翻译单元中重载的实例化。

总结

请注意,我的第二个问题是关于为什么第一个版本优于另一个翻译单元中的版本。虽然我的第一个问题是关于将定义移动到另一个翻译单元时如何消除歧义。

在第二个示例中,它没有歧义,因为编译器看不到第二个翻译单元,因此在隐式实例化函数后只编译对 void func<int,int>(int,int) 的调用。 因为,在第二个翻译单元中,您还实例化了 void func<int,int>(int,int),但定义不同,它是 IFNDR(Ill-Formed 无需诊断)。链接器可能会选择任何定义,因此您的程序将具有未定义的行为。

How is the ambiguity resolved when i moved the definition of the second overload to a different translation unit?

您不仅将定义移动,而且将第二个重载的唯一声明移动到第二个翻译单元中。每个翻译单元现在只知道其中一个重载。

重载解析仅考虑可通过名称查找从函数调用的上下文中找到的声明作为候选。所以在第一个翻译单元中,只有第一个重载会被找到作为候选,而在第二个翻译单元中,只有第二个重载可以被找到。

因此,重载决议将只有一个可行的候选者可供选择。不可能有歧义。

重载解析取决于引入的声明不是问题。仅当它违反 ODR 时才会成为问题,例如因为相同 inline 函数的定义进行的调用会导致不同的重载解析,从而导致两个翻译单元。

I mean we still have two intantiations(one from the overload in the main.cpp and other from the overload in source2.cpp) as before but now we're not getting the ambiguity error.

有两个实例化,但它们是不同函数模板的实例化,因此是不同的函数。这没有理由成为问题。对于哪个函数调用调用哪个模板特化已经由重载决议决定。没有机会混淆他们。

how does the C++ standard resolves this ambiguity.

来自temp.over.link#1

1. It is possible to overload function templates so that two different function template specializations have the same type.

2. Such specializations are distinct functions and do not violate the one-definition rule.

(强调我的)

现在,在给定的示例中,由两个重载产生的两个特化将具有相同的类型 void (int, int) 并且如上文所述,这种用法是允许的。


How does the C++ standard allows the first overload to be selected/preferred instead of the second.

要回答第二个问题,在重载解析期间,source2.cpp 中函数 init 内的调用 func(2,2) 已经绑定到实例化来自第二次重载的版本。类似地,对于 main.cpp 中的调用表达式 func(2,2),它绑定到第一个重载的实例化版本。 因此,当从 main.cpp 内部调用 init 时,将调用第二个版本。如果我们更改 main.cpp 中调用的顺序,那么输出将被反转,因为在重载解析期间调用已经绑定到它们各自的版本。