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

153 Views Asked by At

I was using function templates when I noticed that moving the definition of one of the function template to a different translation unit resolves the ambiguous error. Below are the two examples that I tried. The first example produces ambiguous error as expected but when I move the definition of one of the function template into another translation unit then the error is gone.

Example 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 showing that we get ambiguous error as expected.

My question is about the second example given below:

Example 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);
}

The second version given above successfully compiles and produces the output:

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

My questions are:

  1. How is the ambiguity resolved when i moved the definition of the second overload to a different translation unit? 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. So how does the C++ standard resolves this ambiguity.

  2. How does the C++ standard allows the first overload to be selected/preferred instead of the second. I mean is there a reference in the standard that says that the instantiation from the overload in the same translation unit should be selected.

Summary

Note that my second question is about why the first version is preferred over the one in the other translation unit. While my first question is about how is the ambiguity removed when moving the definition to another translation unit.

3

There are 3 best solutions below

0
On BEST ANSWER

how does the C++ standard resolves this ambiguity.

From 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.

(emphasis mine)

Now, in the given example both the specializations resulting from the two overloads will have the same type void (int, int) and as quoted in the points above, this usage is allowed.


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

To answer the second question, during overload resolution the call func(2,2) inside function init in source2.cpp was already bound to the instantiated version from the second overload. Similarly, for the call expression func(2,2) inside main.cpp, it is bound to the instantiated version from the first overload. Thus, when init is called from inside main.cpp, the second version is called. If we changed the order of the calls inside main.cpp, then the output will be reversed because the call was already bound to their respective version during overload resolution.

2
On

In the second example, it's not ambiguous because the compiler doesn't see the second translation unit and therefore just compiles a call to void func<int,int>(int,int) after implicitly instantiating the function. As, in the second translation unit you're also instantiating void func<int,int>(int,int) but with a different definition, it's IFNDR (Ill-Formed No Diagnostic Required). The linker might choose any definition and therefore your program will have undefined behavior.

2
On

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

You moved not only the definition, but also the only declaration of the second overload into the second translation unit. Each translation unit is now aware of only one of the overloads.

Overload resolution considers only the declarations which can be found by name lookup from the context of the function call as candidates. So in the first translation unit only the first overload will be found as candidate and in the second translation unit only the second overload can be found.

The overload resolution will therefore have only ever one viable candidate to choose from. There is no possibility for ambiguity.

It is not a problem that overload resolution depends on which declarations have been introduced. This would be an issue only if it violated ODR, e.g. because definitions for the same inline function make a call that results in different overload resolution results in two translation units.

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.

There are two instantiations, but these are instantiations of different function templates and therefore different functions. There is no reason for this to be an issue. Which of the template specializations is called for which function call has already been decided by overload resolution. There is no chance of confusing them.