Why template function only base the return type works on C++?

Consider this code:

int    foo(void) { return 1; }
double foo(void) { return 1.0; }

Then (assume) when you call foo(), the compiler would see two candidates for the overload resolution, and has no way to tell which one you want, nor do you have any way to clarify which function you want, so this is forbidden at the point of definition.

But in your code, when you call add<int>(1.1, 1), the compiler sees only one candidate as you've explicitly specified the template parameter, which is ::add<int>(double, double), so there's no overloading here and therefore nothing goes wrong.

Õ the other hand, the following code will cause the same confusion as the first part of the answer:

template int add<int>(double, double);
template double add<double>(double, double);

cout << add(1.1, 1);

The first two lines of the above snippet explicitly instantiates the template function for two template parameters, and the last line brings up the overload resolution, which fails because there's no way to differ the two instances. But you have another option to de-ambiguate this function call (specify the template parameter), that's why the top two lines can compile.


User StoryTeller gave the best straight up answer coming from the standard. I would like to elaborate on this by giving a break down example of how compilers treat this:


Let's look at your current code:

#include <iostream>
using namespace std;

template<typename T>
T add(double a, double b) {
    return static_cast<T>(a + b); 
}

int main() {
    cout << add<int>(1.1, 1) << endl;
    cout << add<double>(1.1, 1) << endl;
    return 0;
}

Let's see how the compiler will treat this. Before we do that, remember this: templates have to be known at compile time and similar to how C++ replaces text with macros and defines it does something of that nature for templates as well when they become instantiated.

Your function template has this signature: this will generate which ever function it needs to satisfy T.

template<typename T>
T add(double a, double b) {
    return static_cast<T>(a + b); 
}

However in this case T is not a part of the signature. The function's signature looks like this:

::add<T>(double, double)

And since the templates argument refers to its return type as opposed to one of its parameters it has no effect here.


Let's look at this as if we weren't using templates. For demonstration purposes only: ignore the fact that the following will create ambiguous functions:

int add( double, double );
float add( double, double );
double add( double, double );

Now let's apply the function calls in your main without the template version:

#include <iostream>

int main() {
    std::cout << add( 1.1, 1 ) << '\n';  // <int> - reminder of original
    std::cout << add( 1.1, 1 ) << '\n';  // <double> -     ""
    return 0;
}

Now looking at the code above, you have the same exact function call. So which overload does the add in this case call? It's quite simple; without using a template and ignoring the ambiguity, the above function would call double add( double, double ).

Since the above would generate a compiler error due to being ambiguous, let's go back and apply the template to investigate why this ambiguity doesn't happen with the template version.


-Original Code-

#include <iostream>

template<typename T>
T add( double a, double b ) {
    return static_cast<T>( a + b );
}

int main() {
    std::cout << add<int>(1.1, 1) << '\n';
    std::cout << add<double>(1.1,1) << '\n';
    return 0;
}

Let's see how the compiler treats this in a step by step fashion:


-Step 1: - Name Resolution, acquiring the function signature.

int main() {
    std::cout << ::add<int>( 1.1, 1 ) << '\n';
    std::cout << ::add<double>( 1.1, 1 ) << '\n';
    return 0;
}

-Step 2: - Calling the function, and creating the function's call stack

int main() {
    std::cout << 
        ::add<int>( 1.1, 1 ) {
           return static_cast<int>( 1.1 + 1 );
        }
              << '\n';

    std::cout <<
        ::add<double>( 1.1, 1 ) {
            return static_cast<double>( 1.1 + 1 );
        }
              << '\n';

    return 0;
}

-Step 3: - Performing all of the instructions within the function

int main() {
    std::cout << 
        /*::add<int>( 1.1, 1 ) {
           return static_cast<int>( 1.1 + 1 );
        }*/
           return static_cast<int>( 2.1 ); 
              << '\n';

    std::cout <<
        /*::add<double>( 1.1, 1 ) {
            return static_cast<double>( 1.1 + 1 );
        }*/
            return static_cast<double>( 2.1 );
              << '\n';
    return 0;
}

-Step 4: - Returning the result back from the function and cleaning up the function call stack

int main() {
    std::cout << 
            return 2; 
              << '\n';

    std::cout <<
            return 2.1;
              << '\n';
    return 0;
}

-Step 5: - Main function is passing the returned results into the stream operators to the standard screen output.

int main() {
    std::cout << 2 << '\n';
    std::cout << 2.1 << '\n';
    return 0;
}

And this matches your output exactly!

-Output-

2
2.1

I hope this break down helps you to understand templates better and to see why there is no ambiguity here as if you didn't use them. The bottom line here is that there is no ambiguity due to the fact that you explicitly instantiated the function templates.

Now try to run your program again but this time don't specify a type and let the compiler implicitly instantiate the function template. I believe you would get a compiler error!


The reason you cannot overload based on return type alone is that the return type is not part of a functions signature, unlike the parameter types. Don't take my word for it, the C++ standard says as much:

[defns.signature]

⟨function⟩ name, parameter-type-list, and enclosing namespace (if any)

[ Note: Signatures are used as a basis for name mangling and linking. — end note ]

But for function template specializations, be they generated implicitly or explicitly, the signature contains the argument(s):

[defns.signature.spec]

⟨function template specialization⟩ signature of the template of which it is a specialization and its template arguments (whether explicitly specified or deduced)

So for add<int>, the int becomes part of the signature. Not because it's the return type, but because it's the template argument. Same for add<double>. And so long as the signatures are different, those can be identified as different functions, and therefore may be overloaded on the same name.

Tags:

C++

Templates