代码之家  ›  专栏  ›  技术社区  ›  Jeremy Friesner

是否有方法使用sfinae来检测类型是否实现了给定的抽象基类?

  •  0
  • Jeremy Friesner  · 技术社区  · 8 年前

    下面是这个问题的简单用例(注意:我意识到这里显示的代码模式可能不被认为是最佳实践;示例代码只用于说明手头的主题)

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

    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());
    }
    

    …这也很有效,只要我小心地只传递给它一个参数,它是对一个可实现对象的引用。

    但现在,我想做一个 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() 在运行时测试对象,如果可能的话,我希望通过在编译时评估可实现性测试来避免这种开销。

    我的问题是,有没有办法使用SFINAE来实现 CloneAnyObject() 功能正常吗?

    (注意,我知道测试一个类型名是否有一个具有给定名称的方法的sfinae技巧,我认为这是一种非常有用的技术,但这并不是我在这里要寻找的;相反,我要寻找的是测试一个类型是否从接口继承的方法)

    2 回复  |  直到 8 年前
        1
  •  3
  •   nwn    8 年前

    你可以使用 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,还可以简化使用 if constexpr 如所见 Jarod42 的答案。

        2
  •  1
  •   Jarod42    8 年前

    可能有两个重载:

    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);
        }
    }