Reputation: 791
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
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
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
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
Reputation: 170064
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:
⟨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):
⟨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
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