I want a setup like the following:
template <typename T> class a {};
class b : public a<int> {};
template <typename T>
void do_foo(std::unique_ptr<a<T>> foo)
{
// Do something with foo
}
int main()
{
do_foo(std::make_unique<b>());
}
This fails to compile with a note saying template argument deduction/substitution failed
and mismatched types 'a<T>' and 'b'
. It's pretty self-explanatory. I can help the compiler along by writing do_foo<int>(std::make_unique<b>());
, but then I'm repeating myself by writing int
twice.
Is there a way to get the compiler to deduce the template parameter in this case? And what would you call this behaviour? I tried searching for things like "template type deduction for inherited type", "polymorphic template deduction" etc.
Is there a way to get the compiler to deduce the template parameter in this case?
No. Not in C++14 (or even C++20).
And what would you call this behaviour?
Standard compliant. To be specific, this paragraph applies:
[temp.deduct.call]
4 In general, the deduction process attempts to find template argument values that will make the deduced
A
identical toA
(after the typeA
is transformed as described above). However, there are three cases that allow a difference:
- If the original
P
is a reference type, the deducedA
(i.e., the type referred to by the reference) can be more cv-qualified than the transformedA
.- The transformed
A
can be another pointer or pointer to member type that can be converted to the deducedA
via a qualification conversion ([conv.qual]).- If
P
is a class andP
has the form simple-template-id, then the transformedA
can be a derived class of the deducedA
. Likewise, ifP
is a pointer to a class of the form simple-template-id, the transformedA
can be a pointer to a derived class pointed to by the deducedA
.
This is an exhaustive list of cases where a template argument can be validly deduced from a function argument even if it doesn't match the pattern of the function parameter exactly. The first and second bullets deal with things like
template<class A1> void func(A1&){}
template<class A2> void func(A2*){}
int main() {
const int i = 1;
func(i); // A1 = const int
func(&i); // A2 = const int
}
The third bullet is the one that is closest to our case. A class derived from a template specialization can be used to deduce a template parameter pertaining to its base. Why doesn't it work in your case? Because the function template parameter is unique_ptr<a<T>>
and the argument you call it with is unique_ptr<b>
. The unique_ptr
specializations are not themselves related by inheritance. So they don't match the bullet, and deduction fails.
But it doesn't mean that a wrapper like unique_ptr
prevents template argument deduction entirely. For instance:
template <typename> struct A {};
struct B : A<int> {};
template<typename> struct wrapper{};
template<> struct wrapper<B> : wrapper<A<int>> {};
template<typename T>
void do_smth(wrapper<A<T>>) {}
int main() {
do_smth(wrapper<B>{});
}
In this case, wrapper<B>
derives from wrapper<A<int>>
. So the third bullet is applicable. And by the complex (and recursive) process of template argument deduction, it allows B
to match A<T>
and deduce T = int
.
TL;DR: unique_ptr<T>
specializations cannot replicate the behavior of raw pointers. They don't inherit from the specializations of unique_ptr
over T
's bases. Maybe if reflection ever comes to C++, we'll be able to meta-program a smart pointer that does behave that way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With