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

c++ function as template parameter
c template return type
c++ template function
c++ type function
extern template function c++
static template function
calling function with template
c++ template function prototype

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!

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.

Function template, function-declaration-with-placeholders(since C++20), -, a function extern template return-type name < argument-list > ( parameter-list ) ;, (3), (since C++11) after the template definition, and for a given argument-list, is only allowed to Whenever the arguments are some C++ basic types, there are no  This only works with the new return value syntax because under the old syntax, we couldn't refer to the function argument, builder, at the point where we declare the return type. With the new syntax, all of the arguments to a function are fair game!

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!

Templates, C++ FAQ, My template function does something special when the template type T is int or std::string uses a nested type it inherits from its template-base-class? int len() const { return len_; }; const int& operator[](int i) const { return data_[check(i)]; } code that works for all T types switch (typeof(T)) { // Conceptual only; not C++  The difference is, compiler does type checking before template expansion. The idea is simple, source code contains only function/class, but compiled code may contain multiple copies of same function/class. Function Templates We write a generic function that can be used for different data types. Examples of function templates are sort(), max

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.

Templates - C++ Tutorials, A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass  You can use templates to define functions as well as classes, let us see how they work − Function Template. The general form of a template function definition is shown here − template <class type> ret-type func-name(parameter list) { // body of function } Here, type is a placeholder name for a data type used by the function.

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)

Overloads and templates - C++ Tutorials, In C++, two different functions can have the same name if their parameters are different; Note that a function cannot be overloaded only by its return type. For each type, non-type, and template parameter, including parameter packs, a unique fictitious type, value, or template is generated and substituted into function type of the template If only one of the two function templates being compared is a member function , and that function template is a non-static member of some class A, a new first

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.

enable_if - 1.65.0, For example, one can define function templates that are only enabled for, and thus only In the previous section, the return type form of enable_if was shown. the form of enable_if that works via an extra function parameter, the foo function in In a compiler which supports C++0x default arguments for function template  However, many data structures and algorithms look the same no matter what type they are operating on. Templates enable you to define the operations of a class or function, and let the user specify what concrete types those operations should work on. Defining and using templates. A template is a construct that generates an ordinary type or

Why Not Specialize Function Templates?, In C++, there are class templates and function templates. These two kinds of templates don't work in exactly the same ways, and the most obvious Finally, let's focus on function templates only and consider the overloading rules to see which If that base template happens to be specialized for the types being used, the  C++14 permits auto to indicate that a function’s return type should be deduced (see Item 3), and C++14 lambdas may use auto in parameter declarations. However, these uses of auto employ template type deduction, not auto type deduction. So a function with an auto return type that returns a braced initializer won’t compile:

1. Deducing Types - Effective Modern C++ [Book], C++98 had a single set of rules for type deduction: the one for function templates. It explains how template type deduction works, how auto builds on that, and how The type deduced for T is dependent not just on the type of expr , but also on the form Each will be based on our general form for templates and calls to it: Templates Function templates Function templates are special functions that can operate with generic types. This allows us to create a function template whose functionality can be adapted to more than one type or class without repeating the entire code for each type. In C++ this can be achieved using template parameters. A template parameter is

C++ template declarations vs. definitions, For example, this is a function declaration, very typical in C and C++ code: named someFunction which takes two doubles as parameter and returns an int. This is also called an "incomplete type" (eg. by the C++ standard). have the mistaken notion that template functions and classes can only be defined, not declared. A function declared to have C linkage can use all the features of C++, but its parameters and return type must be accessible from C if you want to call it from C code. For example, if a function is declared to take a reference to an IOstream class as a parameter, there is no (portable) way to explain the parameter type to a C compiler.

Comments
  • It's plain overloading, but when you specify the template parameter then there's no more overload resolution since there's only 1 candidate function.
  • add<int> and add<double> are two completely separate functions, they have nothing to do with each other (the fact that they are specializations of the same template doesn't matter here). So there is no overloading here. Consider them as if they called add_int and add_double.
  • Define(Declare) int foo(void) { return 1; } and double foo(void) { return 1.0; } cause the compile error: ambiguating declaration even not call one of them. But define template<typename T> T add(double a, double b) { return static_cast<T>(a + b); } is OK.
  • Your foo example is ill-formed at the second foo declaration immediately, not at cout << foo(), so your explanation is incorrect.
  • @sfz That's the point. You have no way to tell the compiler which function you use, so defining like that is forbidden directly, but for the second case you have a chance to make it clear, so it doesn't error out at definition point.
  • "the compiler sees two candidates for the overload resolution" - I don't think that overload resolution is the correct term here.
  • @Bathsheba I don't know, what else could be?