Here is a simplified example:
#include <memory>
#include <vector>
template< class T >
class K
{
public:
virtual ~K(){}
};
class KBOUM : public K<int>{};
template< class U >
void do_something( std::shared_ptr< K<U> > k ) { }
int main()
{
auto kboom = std::make_shared<KBOUM>();
do_something( kboom ); // 1 : error
std::shared_ptr< K<int> > k = kboom; // 2 : ok
do_something( k ); // 3 : ok
}
With or without boost, whatever the compiler I use I get an error on #1 because shared_ptr<KBOOM> don't inherit from shared_ptr<K<int>> .
However, KBOOM does inherit from K<int>. You can see that #2 works because shared_ptr is designed to allow implicitly passing a child class pointer to a base class pointer, like raw pointers.
So my questions are:
auto kboom = std::make_shared<KBOUM>(); do_something( kboom ); without looking the int type from K from which KBOOM inherit?Note: I want to avoid the user of the function to have to write
std::shared_ptr<K<int>> k = std::make_shared<KBOOM>();
or
do_something( std::shared_ptr<K<int>>( kboom ) );
This is not related to std::shared_ptr<>. In fact, you could replace that with any class template and get the very same result:
template<typename T> struct X { };
class KBOUM : public X<int> { };
template<typename U>
void do_something(X<K<U>> k) { }
int main()
{
X<KBOUM> kboom;
do_something(kboom); // ERROR!
X<K<int>> k;
do_something(k); // OK
}
The problem here is that type argument deduction is trying to find a perfect match, and derived-to-base conversions are not attempted.
Only after all template parameters have been unambiguously deduce to produce a perfect match (with the few exceptions allowed by the Standard), possible conversions between arguments are considered during overload resolution.
WORKAROUND:
It is possible to figure out a workaround based on a solution posted by KerrekSB in this Q&A on StackOverflow. First of all, we should define a type trait that allow us to tell whether a certain class is derived from an instance of a certain template:
#include <type_traits>
template <typename T, template <typename> class Tmpl>
struct is_derived
{
typedef char yes[1];
typedef char no[2];
static no & test(...);
template <typename U>
static yes & test(Tmpl<U> const &);
static bool const value = sizeof(test(std::declval<T>())) == sizeof(yes);
};
Then, we could use SFINAE to rewrite do_something() as follows (notice that C++11 allows default arguments for function template parameters):
template<class T, std::enable_if<is_derived<T, K>::value>* = nullptr>
void do_something(X<T> k)
{
// ...
}
With these changes, the program will correctly compile:
int main()
{
X<KBOUM> kboom;
do_something(kboom); // OK
X<K<int>> k;
do_something(k); // OK
}
And here is a live example.
Andy Prowl gave a perfect explanation about the issue and suggested a clever workaround. I guess with some effort the workaround can be adapted to C++03 as well. (I haven't tried that. This is just a guess.)
I only want to suggest a simpler workaround which can only work for C++11. All you need to do is create this overload:
template< class T >
auto do_something(const std::shared_ptr<T>& k ) ->
decltype(do_something( std::shared_ptr< K<int> >( k )))
{
return do_something( std::shared_ptr< K<int> >( k ));
}
Basically, it probes if do_something( std::shared_ptr< K<int> >( k )) is legal through a decltype (and SFINAE). If so, then this overload performs the "cast to base" and delegates the call to the overload that takes shared_ptr to the base class.
Update:
More generally, if you have a function, say do_something that accepts a shared_ptr<Base> and you want the compiler to call it when you pass a shared_ptr<T> where T is any type that publicly derives from Base then the workaround is this:
class Base {};
class Derived : public Base {};
// The original function that takes a std::shared_ptr<Base>
void do_something( const std::shared_ptr<Base>& ) {
// ...
}
// The workaround to take a shared_ptr<T> where T publicly derives from Base
template <typename T>
auto do_something(const std::shared_ptr<T>& pd) ->
decltype( do_something( std::shared_ptr<Base>( pd ) ) ) {
return do_something( std::shared_ptr<Base>( pd ) );
}
// Example:
int main() {
auto pd = std::make_shared<Derived>();
do_something( pd );
}
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