有没有一种方法可以使用SFINAE来检测类型是否实现了给定的抽象基类?

时间:2018-07-19 04:48:57

标签: c++ interface sfinae

这是此问题的简单用例。 (注意:我意识到这里显示的代码模式可能不被视为最佳实践;示例代码仅是为了说明当前的主题)

当前,我有这个方便的小模板函数,它将分配堆中任意给定对象的副本,并返回该副本:

template<typename T> inline T * CloneStaticObject(const T & item) 
{
   return new T(item);
}

非常适合使用复制构造函数的任何类型...直到我传递了对基类的引用类型,然后又遇到了对象切片问题。

为解决这个问题,我还有一个很好的OOP风格的克隆接口,可以使用,它带有一个CloneDynamicObject(const ICloneable &)函数,该函数将正确克隆从ICloneable继承的任何对象,不管传入的引用是什么类型:

class ICloneable
{
public:
   virtual ~ICloneable() {/* empty */}

   virtual ICloneable Clone() const = 0;
};

class MyBaseClass : public ICloneable
{
public:
   MyBaseClass() {/* empty */}

   virtual ICloneable * Clone() const {return new MyBaseClass(*this);}      
};

class MySubClass : public MyBaseClass
{
public:
   MySubClass() {/* empty */}

   virtual ICloneable * Clone() const {return new MySubClass(*this);}      
};

template<typename T> inline T * CloneDynamicObject(const T & item) 
{
   return static_cast<T *>(item.Clone());
}

...这也很好用,只要我小心地只给它传递一个引用到ICloneable对象的参数即可。

但是现在已经有了渐进的优雅,我想创建一个CloneAnyObject(const T & item)函数,无论发生什么事情,它都会做正确的事情,例如:

 // This doesn't work but it shows the idea
 template<typeName T> inline T * CloneAnyObject(const T & item)
 {
    const ICloneable * cloneMe = dynamic_cast<const ICloneable *>(&item);
    if (cloneMe) return CloneDynamicObject(*cloneMe)
            else return CloneStaticObject(item);
 }

...上面的实现几乎可以满足我的要求,除了它有两个问题:

  1. dynamic_cast<>不适用于所有类型
  2. 即使这样做,dynamic_cast()也会在运行时测试对象,如果可能的话,我希望通过在编译时评估ICloneability来避免这种开销。
  3. li>

我的问题是,有什么方法可以使用SFINAE正确实现CloneAnyObject()函数吗?

(请注意,我知道SFINAE的技巧,用于测试类型名称是否具有给定名称的方法,并且我认为这是一种非常有用的技术,但这并不是我在这里寻找的; '我正在寻找的是一种测试类型是否从接口继承的方法

2 个答案:

答案 0 :(得分:3)

您可以使用std::is_base_of特性来静态确定某个类型是否派生自另一个类型。可以使用以下方法将您的函数分为一个版本(派生类型)和另一个版本(其他类型):

// For derived types
template <typename T, std::enable_if_t<std::is_base_of_v<ICloneable, T>, int> = 0>
inline T * CloneAnyObject(const T & item) {
    return CloneDynamicObject(item);
}

// For non-derived types (note the !)
template <typename T, std::enable_if_t<!std::is_base_of_v<ICloneable, T>, int> = 0>
inline T * CloneAnyObject(const T & item) {
    return CloneStaticObject(item);
}

如果您使用的是C ++ 17,则也可以使用Jarod42的答案中的if constexpr来简化此操作。

答案 1 :(得分:1)

您可能有2个重载:

template <typeName T>
std::enable_if_t<std::is_base_of<ICloneable , T>::value, T*> /* SFINAE */
CloneAnyObject(const T& item)
{
    return CloneDynamicObject(item);
}

template <typeName T>
std::enable_if_t<!std::is_base_of<ICloneable , T>::value, T*> /* SFINAE */
CloneAnyObject(const T& item)
{
    return CloneStaticObject(item);
}

使用C ++ 17,您可能会做

template <typeName T>
T* CloneAnyObject(const T& item)
{
    if constexpr (std::is_base_of<ICloneable, T>::value) {
        return CloneDynamicObject(item);
    } else {
        return CloneStaticObject(item);
    }
}