Consider the following template functions:
template <class T>
const T* DoSomething(const T& t)
{
auto& id = typeid(T);
cout << "Type is " << id.name() << ", and we have a ";
cout << "ref of one\n";
return &t;
}
template <class T>
T* DoSomething(T* t)
{
auto& id = typeid(T);
cout << "Type is " << id.name() << ", and we have a ";
cout << "pointer to one \n";
return t;
}
template <class T, template <class> class container>
T* DoSomething(const container<T>& t)
{
auto& type_id = typeid(T);
auto& container_id = typeid(container<T>);
cout << "Type is " << type_id.name() << ", and we have a ";
cout << container_id.name() << "of one\n";
return t.get();
}
template <class T, template <class,class> class container, template <class> class deleter = default_delete>
T* DoSomething(const container<T, deleter<T>>& t)
{
auto& type_id = typeid(T);
auto& container_id = typeid(container<T,deleter<T>>);
cout << "Type is " << type_id.name() << ", and we have a ";
cout << container_id.name() << "of one\n";
return t.get();
}
The goal is to be able to pass to them either a plain reference, pointer, or smart pointer, and use overloading and template specilization in order for the correct function to be called. The following driving code works as expected:
char r('r');
DoSomething(r);
DoSomething(&r);
shared_ptr<char> s(new char ('s'));
unique_ptr<char> u(new char ('u'));
DoSomething(s);
DoSomething(u);
However, consider what happens if we try this:
vector<int> v {1,2};
DoSomething(v);
Now, we we get a compile error. The version of DoSomething that the compiler decides to use is the 4th one. Inside this, we reference a function get(), which vector does not have. If the compiler were to somehow pick the 1st defintion of DoSomething, it would compile fine, and work as I intend.
So can I restrict the 3rd and 4th specializations to only be matched when the template template parameter contains a get() method? Is there a way I can get this to happen, perhaps using traits, SFINAE, or some other more advanced template technique?
The version of DoSomething that the compiler decides to use is the 4th one.
Because std::vector<T, std::allocator<T>>
is an exact match for the template parameter container
and std::allocator<T>
is an exact match for the template parameter deleter
, and const container<T, deleter<T>>&
is more specialized than const T&
so the 4th overload is chosen as the best match by the partial ordering rules for function templates.
So can I restrict the 3rd and 4th specializations to only be matched when the template template parameter contains a get() method?
Yes, you can tell the compiler that the function returns whatever t.get()
returns:
template <class T, template <class> class container>
auto DoSomething(const container<T>& t) -> decltype(t.get())
{
auto& type_id = typeid(T);
auto& container_id = typeid(container<T>);
cout << "Type is " << type_id.name() << ", and we have a ";
cout << container_id.name() << "of one\n";
return t.get();
}
If t.get()
is not a valid expression then template argument deduction fails, because the argument T
cannot be substituted into the function signature successfully, and so the function will not be a viable overload, and the 1st overload will be used instead.
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