#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
}
无论使用或不使用boost,无论使用何种编译器,我都会在#1上收到错误,因为shared_ptr<KBOOM>
不会从shared_ptr<K<int>>
继承。
但是,KBOOM
确实从K<int>
继承。您可以看到#2有效,因为shared_ptr旨在允许将子类指针隐式传递给基类指针,就像原始指针一样。
所以我的问题是:
auto kboom = std::make_shared<KBOUM>(); do_something( kboom );
?注意:我想避免该函数的用户必须写
std::shared_ptr<K<int>> k = std::make_shared<KBOOM>();
或
do_something( std::shared_ptr<K<int>>( kboom ) );
答案 0 :(得分:6)
这与std::shared_ptr<>
无关。实际上,您可以将其替换为任何类模板并获得完全相同的结果:
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
}
这里的问题是类型参数推断试图找到完美匹配,并且不尝试派生到基础的转换。
之后只有所有模板参数被明确推断以产生完美匹配(标准允许的少数例外),在重载解析期间可以考虑参数之间的可能转换。
解决方法:强>
可以根据KerrekSB在this Q&A on StackOverflow中发布的解决方案找出解决方法。首先,我们应该定义一个类型特征,它允许我们判断某个类是否来自某个模板的实例:
#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);
};
然后,我们可以使用SFINAE重写do_something()
,如下所示(注意C ++ 11允许函数模板参数的默认参数):
template<class T, std::enable_if<is_derived<T, K>::value>* = nullptr>
void do_something(X<T> k)
{
// ...
}
通过这些更改,程序将正确编译:
int main()
{
X<KBOUM> kboom;
do_something(kboom); // OK
X<K<int>> k;
do_something(k); // OK
}
这是一个live example。
答案 1 :(得分:1)
Andy Prowl对这个问题做了一个完美的解释,并提出了一个聪明的解决方法。我想通过一些努力,解决方法也可以适应C ++ 03。 (我没试过。这只是猜测。)
我只想提出一个更简单的解决方法,它只适用于C ++ 11。您需要做的就是创建这个重载:
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 ));
}
基本上,它会通过do_something( std::shared_ptr< K<int> >( k ))
(和SFINAE)调查decltype
是否合法。如果是这样,那么这个重载执行“cast to base”并将调用委托给将shared_ptr
带到基类的重载。
<强>更新强>
更一般地说,如果您有一个函数,请说do_something
接受shared_ptr<Base>
,并且您希望编译器在传递shared_ptr<T>
时调用它T
任何公开来自Base
的类型,然后解决方法是:
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 );
}
答案 2 :(得分:0)
#include <memory>
#include <utility>
template< template<typename T>class Factory, typename T >
struct invert_factory {};
template< template<typename T>class Factory, typename U >
struct invert_factory< Factory, Factory<U> > {
typedef U type;
};
template<typename T>
struct K {};
template<template<typename>class Factory, typename Default, typename U>
U invert_implicit_function( Factory<U> const& );
template<template<typename>class Factory, typename Default>
Default invert_implicit_function( ... );
template<template<typename>class Factory, typename U>
struct invert_implicit {
private:
struct unused_type{};
public:
typedef decltype( invert_implicit_function<Factory, unused_type>( std::declval<U>() ) ) type;
enum{ value = !std::is_same< unused_type, type >::value };
};
template<typename spKU, typename=void >
struct is_shared_ptr_to_KU {};
template<typename spKU>
struct is_shared_ptr_to_KU< spKU,
typename std::enable_if<
invert_implicit< K,
typename invert_factory<std::shared_ptr, spKU>::type
>::value
>::type
>:std::true_type {};
template< typename spKU >
auto do_something( spKU )->typename std::enable_if< is_shared_ptr_to_KU<spKU>::value >::type { }
struct Blah:K<int> {};
int main() {
static_assert(invert_implicit< K, K<int> >::value, "one");
static_assert(invert_implicit< K, Blah >::value, "two");
do_something( std::shared_ptr<K<int>>() );
do_something( std::shared_ptr<Blah>() );
// do_something( 0 );
// do_something( std::shared_ptr<int>() );
}
需要一点点润色,但要做的就是你所要求的。
处理shared_ptr<K<U>>
需要额外的间接性。
还包括如果需要实际提取U
类型的方法。 (如果invert_implicit::value
为真,则invert_implicit::type
为U
)。
请注意,可隐式转换为K<U>
的类符合条件 - 如果您愿意,也可以执行检查is_derived
。