Why does the following code work?
#include <iostream>
#include <cstdint>
template <class D>
void whichD() {
std::cout << "D is " << sizeof(D) << " byte(s)\n";
}
int main(int argc, char **argv)
{
if (argv[1][0] == '1') {
whichD<uint8_t>();
}
else {
whichD<uint16_t>();
}
}
./temp 1 && ./temp 2
D is 1 byte(s)
D is 2 byte(s)
I did not expect it to work, for the same reason that you can't have:
void foo() { /* ... */ }
void foo() { /* ... */ } // error: re-definition
int bar() { /* ... */ }
double bar() { /* ... */ } // error: re-definition
It's true that functions have to have a different signature. Otherwise, they are re-definitions of each other. However, what constitutes the signature depends on the kind of entity.
For non-template functions, the signature is defined as:
signature
⟨function⟩ name, parameter-type-list, and enclosing namespace
For example:
void foo()
and void bar()
have a different signature, because they have a different namevoid foo(int)
and void foo(float)
have a different parameter-type-listvoid a::foo()
and void b::foo()
have a different enclosing namespacevoid foo()
and int foo()
are re-definitions of each other; the return type is not part of the signature, so these functions aren't distinct.A function template (e.g. whichD
) with given template arguments (e.g. whichD<uint8_t>
) is called a function template specialization.
The signature of a function template specialization is:
signature
⟨function template specialization⟩ signature of the template of which it is a specialization and its template arguments (whether explicitly specified or deduced)
- [defns.signature.templ.spec]
In your example, whichD<uint8_t>
and whichD<uint16_t>
are specializations of the same function template but their template arguments are different, making them distinct functions.
The compiler doesn't just drop all the templatey-ness and use the template to produce:
void whichD() { /* ... */ }
void whichD() { /* ... */ } // error: re-definition
Instead, the compiler will instantiate these specializations, arguments included, and produce something like:
void whichD<uint8_t>() { /* ... */ }
void whichD<uint16_t>() { /* ... */ } // OK, different from above
The signatures of these two specializations would get mangled, so uint8_t
and uint16_t
are included (in some mangled form) in the symbol that the linker sees.
Keep in mind that these rules exist to distinguish cases where it's possible to call functions separately, and where it isn't. If whichD<uint8_t>
and whichD<uint16_>
weren't specializations, but regular functions with the name whichD
, you would be unable to call one of the two.
Overload resolution wouldn't be able to distinguish them.
However, as you have demonstrated in your code, it is easily possible to call whichD<uint8_t>
and whichD<uint16_t>
separately by providing the template arguments explicitly.
It would be an arbitrary restriction to say that these two have the same signature.