sfzhang
sfzhang

Reputation: 791

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

As I know, overloading functions must contain different arguments(type or count). So I think the template function should not only base on the return type. However the following code works on GCC 6.3.0.

#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;
}

Build and run:

g++ -g -o test test.cpp
./test
2
2.1

Dose the C++ standard clarify this? Thanks!

Upvotes: 9

Views: 4954

Answers (5)

上山老人
上山老人

Reputation: 462

I have try it use the method from here

First, give the test code:

template < class T> T add(T a, T b){
            return a+b;
}

void tmp(){
    add<int>(10, 2);
}

int add(int a, int b)
{
    return a + b;
}

Then input the commond:

gcc -S -O1 test.cpp

At last, i'll get the content below:

    .file   "compile2.cpp"
    .text
    .globl  _Z3tmpv
    .type   _Z3tmpv, @function
_Z3tmpv:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $2, %esi
    movl    $10, %edi
    call    _Z3addIiET_S0_S0_
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   _Z3tmpv, .-_Z3tmpv
    .globl  _Z3addii
    .type   _Z3addii, @function
_Z3addii:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -8(%rbp), %eax
    movl    -4(%rbp), %edx
    addl    %edx, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2:
    .size   _Z3addii, .-_Z3addii
    .section    .text._Z3addIiET_S0_S0_,"axG",@progbits,_Z3addIiET_S0_S0_,comdat
    .weak   _Z3addIiET_S0_S0_
    .type   _Z3addIiET_S0_S0_, @function
_Z3addIiET_S0_S0_:
.LFB3:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -8(%rbp), %eax
    movl    -4(%rbp), %edx
    addl    %edx, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE3:
    .size   _Z3addIiET_S0_S0_, .-_Z3addIiET_S0_S0_
    .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"
    .section    .note.GNU-stack,"",@progbits

And, we can find two diffrent function signature _Z3addii and _Z3addIiET_S0_S0_

[root@localhost template]# c++filt _Z3addIiET_S0_S0_
int add<int>(int, int)
[root@localhost template]# c++filt _Z3addii
add(int, int)

Upvotes: 1

Francis Cugler
Francis Cugler

Reputation: 7905

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!

Upvotes: 4

Bathsheba
Bathsheba

Reputation: 234685

The capabilities of templates in C++ have evolved in a haphazard way since their first inception (which essentially allowed little more than to allow us to write generic container classes). Following this the C++ programming community soon put them to other usages (e.g. metaprogramming techniques).

The ability to instantiate different functions based on the return type alone is allowed since the C++ standards committee (actually Bjarne himself before handing over control of the language) deemed it useful. It is: if only std::accumulate worked that way rather than deriving the return type from the type of the variable offering up the initial value!

So useful infact that from C++11 we can even use a trailing return type syntax to allow the compiler to derive the return type when it's only discoverable by inspecting the function parameter list and, in later standards, the function contents.

Note one myth that needs to be debunked: in your case add<double>(double, double) and add<int>(double, double) are not function overloads (how could they be? - the names, airity, and parameter types are identical), but rather are different instantiations of the template function.

Upvotes: 0

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.

Upvotes: 19

iBug
iBug

Reputation: 37227

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.

Upvotes: 1

Related Questions