Reputation: 2261
I thought that the function overload with the most specific matching argument types would be called, but it seems I don't understand an aspect of type deduction when template and inherited types are combined.
Example:
#include<iostream>
#include<typeinfo>
struct Foo {};
struct Bar : Foo {};
#ifdef FOO
void print_typeid( const Foo& f ) {
std::cout << "(F) typeid: " << typeid(f).name() << std::endl;
}
#endif // FOO
#ifdef GENERIC
template<typename Generic>
void print_typeid( const Generic& g ) {
std::cout << "(G) typeid: " << typeid(g).name() << std::endl;
}
#endif // GENERIC
int main( int argc, char *argv[] ) {
Foo foo; print_typeid(foo);
Bar bar; print_typeid(bar);
return 0;
}
Test cases
1. Define FOO only
$ g++ -DFOO main.cpp -o foo && ./foo
Output:
(F) typeid: 3Foo
(F) typeid: 3Foo
This makes sense to me as the objects foo
and bar
may be
passed as const Foo&
and since there is no compile-time downcast,
bar
must be identified as having type Foo
.
2. Define GENERIC only
$ g++ -DGENERIC main.cpp -o generic && ./generic
Output:
(G) typeid: 3Foo
(G) typeid: 3Bar
This also makes sense as both foo
and bar
are lvalues and can be passed to the function that takes a generic constant reference. This prints the actual type of each object.
3. Define both FOO and GENERIC
$ g++ -DFOO -DGENERIC main.cpp -o both && ./both
Output:
(F) typeid: 3Foo
(G) typeid: 3Bar
This one confuses me. Having already established that both objects may be passed to both functions, I expected that because const Foo&
is a more specific compatible type for bar
that we would have had the same output as in Case 1. Why does this happen?
Tested using gcc 7.2 and clang 4
Upvotes: 0
Views: 93
Reputation: 72271
First, typeid
only acts polymorphically if the argument has a polymorphic class type. And neither Foo
nor Bar
is polymorphic, since they do not have any virtual functions or virtual base classes. So both your print_typeid
functions are not looking at the actual type of the object, just the declared type of the expression. If you added virtual ~Foo() = default;
to class Foo
, you would see different behavior.
Only FOO
: The declared type of f
is const Foo&
, so you get the typeinfo
for Foo
in both cases.
Only GENERIC
: In print_typeid(foo);
the deduced type of template parameter is Foo
, so you get the typeinfo
for Foo
. But in print_typeid(bar);
the deduced type is Bar
, and you get the typeinfo
for Bar
.
Both FOO
and GENERIC
: It's true that a non-template function beats a template function and a more specialized template function beats a less specialized template function in overload resolution, IF the functions would otherwise be ambiguous. But this rule only kicks in when the implicit conversion sequences for both calls are close enough to the same that neither can be seen as better based on the argument types and parameter types.
For print_typeid(foo)
, the compiler first does type deduction for the function template, getting GENERIC=Foo
. So the specialization of the function template is a potential function with signature void print_typeid(const Foo&);
. Since this is identical to the non-template function, the non-template function wins.
For print_typeid(bar)
, the compiler again does type deduction, this time getting GENERIC=Bar
. The specialization of the function template has signature void print_typeid(const Bar&);
. So calling the non-template function requires a derived-to-base conversion, where calling the template specialization has just a qualification conversion (adding const
). The qualification conversion is better, so the template wins overload resolution.
Upvotes: 1
Reputation: 66190
This one confuses me. Having already established that both objects may be passed to both functions, I expected that because
const Foo&
is a more specific compatible type forbar
that we would have had the same output as in Case 1. Why does this happen?
But const Generic &
, when Generic
is deduced as Bar
, is a better match (is an exact match) for a Bar
object than a const Foo &
.
So the template version of print_typeid()
is preferred and selected whan called with a Bar
object.
On the contrary, calling print_typeid()
with a const Foo &
object, both version matches, as exact matches, and the not-template version is preferred over the template version.
Upvotes: 2