Reputation: 1284
I had the following scheme in my code,
#include <memory>
#include <iostream>
template <class T>
struct A {};
template <class T>
struct Z {};
template <class T, class U>
struct B : A<T>, Z<U> {};
template <class T>
struct C : B<T, T> {};
template <class T>
void foo(const std::shared_ptr<A<T>>& a)
{
std::cout << "HI !" << std::endl;
}
int main()
{
auto c = std::make_shared<C<char>>();
foo(c);
return 0;
}
But the compiler could not substitute the template parameters properly:
main.cpp: In function 'int main()':
main.cpp:26:10: error: no matching function for call to 'foo(std::shared_ptr<C<char> >&)'
foo(c);
^
main.cpp:17:6: note: candidate: template<class T> void foo(const std::shared_ptr<A<T> >&)
void foo(const std::shared_ptr<A<T>>& a)
^~~
main.cpp:17:6: note: template argument deduction/substitution failed:
main.cpp:26:10: note: mismatched types 'A<T>' and 'C<char>'
foo(c);
^
So I changed foo
to this:
template <class T, template <class> class U>
void foo(const std::shared_ptr<U<T>>& a)
{
std::cout << "HI !" << std::endl;
}
It works, but it doesn't feel right as the template constraints on the types are now really loose which usually end up into more obfuscated error message.
Is there a better way to handle these kind of template deduction failures ?
http://coliru.stacked-crooked.com/a/d4ebc14105c184df
Upvotes: 2
Views: 94
Reputation: 217085
In your example, you don't need to transfer ownership, so you should prefer to pass argument by const reference, and then it works as expected:
template <class T>
void foo(const A<T>& a)
{
std::cout << "HI !" << std::endl;
}
int main()
{
auto c = std::make_shared<C<char>>();
foo(*c);
}
If you really need to pass shared_ptr
, see the other answer.
Upvotes: 1
Reputation: 66190
I propose a little improvement of your foo
(the template template one) where the class receive a list of template type parameter; with SFINAE can activated only if the C<T0, Ts...>
class is a derived class of A<T0>
.
template <template <typename...> class C, typename T0, typename ... Ts>
typename std::enable_if<std::is_base_of<A<T0>, C<T0, Ts...>>::value>::type
foo (const std::shared_ptr<C<T0, Ts...>>& a)
{ std::cout << "HI !" << std::endl; }
So you can call foo()
with A<T>
, B<T, U>
and C<T>
but not with Z<T>
or (by example) std::vector<T>
The following is a full compilable example
#include <memory>
#include <vector>
#include <iostream>
template <typename T>
struct A {};
template <typename T>
struct Z {};
template <typename T, typename U>
struct B : A<T>, Z<U> {};
template <typename T>
struct C : B<T, T> {};
template <template <typename...> class C, typename T0, typename ... Ts>
typename std::enable_if<std::is_base_of<A<T0>, C<T0, Ts...>>::value>::type
foo (const std::shared_ptr<C<T0, Ts...>>& a)
{ std::cout << "HI !" << std::endl; }
int main ()
{
foo(std::make_shared<A<char>>());
foo(std::make_shared<B<char, long>>());
foo(std::make_shared<C<char>>());
// foo(std::make_shared<Z<char>>()); // compilation error
// foo(std::make_shared<std::vector<char>>()); // compilation error
}
Upvotes: 1
Reputation: 21510
There is a way. You can choose to constrain the template manually with a bit of template metaprogramming so that only pointer types (can be extended to work with all sorts of pointer types, but only works with both unique_ptr
and shared_ptr
in this case (actually with all smart pointer types that have an element_type
type alias)) of derived classes can be used.
#include <memory>
#include <iostream>
namespace {
/**
* Get the template type of a template template type
*/
template <typename T>
struct GetType;
template <typename T, template <typename...> class TT>
struct GetType<TT<T>> {
using type = T;
};
} // namespace <anonymous>
template <class T>
struct A {};
template <class T>
struct Z {};
template <class T, class U>
struct B : A<T>, Z<U> {};
template <class T>
struct C : B<T, T> {};
template <class T, typename std::enable_if_t<std::is_base_of<
A<typename GetType<typename std::decay_t<T>::element_type>::type>,
typename std::decay_t<T>::element_type>::value>* = nullptr>
void foo(const T&)
{
std::cout << "HI !" << std::endl;
}
int main()
{
auto c = std::make_shared<C<char>>();
auto c_uptr = std::make_unique<C<char>>();
foo(c);
foo(c_uptr);
return 0;
}
This will now work both with unique_ptr
and shared_ptr
. You can chose to make it more general by checking the type of pointer contained (element_type
in the example) by using decltype
with .get()
on the smart pointer, and make a trait that detects if the pointer passed is a primitive pointer and if so just uses the type itself. But you get the point.
Upvotes: 1