3

这是一个简化的示例

#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 旨在允许将子类指针隐式传递给基类指针,就像原始指针一样。

所以我的问题是:

  1. 是什么阻止了 std::shared_ptr 实现者使其在案例 #1 中工作(我的意思是,假设标准确实阻止了这种情况,应该有原因);
  2. 有没有办法在auto kboom = std::make_shared<KBOUM>(); do_something( kboom );不从 KBOOM 继承的 K 中查找 int 类型的情况下进行编写?

注意:我想避免函数的用户必须编写

std::shared_ptr<K<int>> k = std::make_shared<KBOOM>();

或者

do_something( std::shared_ptr<K<int>>( kboom ) );
4

3 回答 3

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
}

这里的问题是类型参数推导试图找到一个完美的匹配,并且没有尝试派生到基础的转换。

只有所有模板参数都被明确地推导出来产生完美匹配(标准允许的少数例外)之后,在重载决议期间才考虑参数之间可能的转换。

解决方法:

可以根据KerrekSBStackOverflow 上的问答中发布的解决方案找出解决方法。首先,我们应该定义一个类型特征,它允许我们判断某个类是否派生自某个模板的实例:

#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
}

这是一个活生生的例子

于 2013-04-04T15:16:36.677 回答
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 ))通过decltype(和 SFINAE)探测是否合法。如果是这样,则此重载执行“转换为基类”并将调用委托给shared_ptr基类的重载。

更新:

更一般地说,如果你有一个函数,说它do_something接受 ashared_ptr<Base>并且你希望编译器在你传递 a 时调用它,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 );   
}
于 2013-04-04T17:12:13.037 回答
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::typeU)。

请注意,可以隐式转换为K<U>限定的类——is_derived如果您愿意,也可以进行检查。

于 2013-04-04T17:30:35.813 回答