I'm trying to create a overload for a function with variadic template arguments for a type and all its possible derivates.
Naively i tried with just one overload and the base class as the first parameter but this doesn't work for derived types. A third overload with a concept or an enable_if can make it work for derived types (and then the first overload wouldn't be needed anymore) but i don't understand why the overload with the base type (the first overload) isn't considered in the overload resolution when called with a derived type.
Can someone explain this?
Here's the code (live demo):
#include <iostream>
struct A {};
struct B : public A {};
template<class... Types>
void func(Types... args)
{
std::cout << "1";
}
template<class... Types>
void func(const A& a, Types... args)
{
std::cout << "2";
}
// why is this needed to cover the derived class?
// with this function enabled, the output is "3" and without "1"
#if 1
template<typename dA, class... Types>
requires std::derived_from<dA, A>
void func(const dA& a, Types... args)
{
std::cout << "3";
}
#endif
int main()
{
func(B{});
}
To maybe make the question clearer; why does this print 1
and not 3
like this does?
When calling func(Derived{})
without the last overload, 1
gets printed instead of 2
because the implicit conversion sequence when calling it is better.
Implicit conversion sequences are more important than which function template is more specialized.
When adding the overload with the std::derived_from
constraint, 1
and 3
have equally good implicit conversion sequences, and 3
is chosen because it is more specialized.
1
is chosen based on implicit conversion sequences#include <iostream>
struct Base {};
struct Derived : public Base {};
void func(auto) { std::cout << "1"; }
void func(const Base& a) { std::cout << "2"; }
int main() {
func(Derived{}); // prints 1
}
Intuitively, func(const Base&)
is a better match because it isn't a template, or because it's intuitively "more specialized" in some other way.
However, func(auto)
is selected. The way overload resolution (see [over.match.general]) works is that
A prvalue of type Derived
is converted to const Base&
through a derived-to-base conversion ([over.ics.ref] p1, [over.best.ics.general] p7), which has the rank Conversion, whereas Derived{} -> auto
is an Exact Match.
Thefore, it's better to call func(auto)
.
3
is chosen based on most specialized function templatestemplate<typename dA, class... Types> requires std::derived_from<dA, A> void func(const dA& a, Types... args)
This case is different.
Since a
binds to any const dA
, it binds to Derived
directly without any derived-to-base conversion.
There is an Exact Match conversion for 1
and 3
.
3
is more specialized because it has a constraint std::derived_from
and is chosen as the best candidate ([over.match.best.general] p2.5).