有没有办法使用 SFINAE 来检测一个类型是否实现了给定的抽象基础 class?
Is there a way to use SFINAE to detect if a type implements a given abstract base class?
这是这个问题的简单用例。 (注意:我意识到此处显示的代码模式可能不被视为最佳实践;示例代码仅用于说明手头的主题)
目前,我有这个方便的小模板函数,它将从堆中分配任意给定对象的副本,return 该副本:
template<typename T> inline T * CloneStaticObject(const T & item)
{
return new T(item);
}
适用于任何具有复制构造函数的类型...直到我传入一个引用到基础-class 类型,然后我遇到对象切片问题。
所以为了处理这个问题,我还有一个很好的 OOP 风格的小克隆界面,我可以使用它,完成一个 CloneDynamicObject(const ICloneable &)
函数,可以正确克隆从 [=15= 继承的任何对象],不管传入的引用是什么类型:
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);
}
...上面的实现几乎做了我想要的,除了它有两个问题:
dynamic_cast<>
不适用于所有类型
- 即使如此,
dynamic_cast()
也会在 运行 时测试对象,如果可能的话,我想通过在编译时评估 test-for-ICloneability 来避免这种开销-时间代替。
我的问题是,有什么方法可以使用SFINAE来正确实现CloneAnyObject()
功能吗?
(请注意,我知道 SFINAE 技巧用于测试类型名称是否具有给定名称的方法,我认为这是一种非常有用的技术,但这并不是我在这里寻找的;我正在寻找的是一种测试类型是否从接口继承的方法)
您可以使用 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
来简化它,如 的回答中所示。
您可能有 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);
}
}
这是这个问题的简单用例。 (注意:我意识到此处显示的代码模式可能不被视为最佳实践;示例代码仅用于说明手头的主题)
目前,我有这个方便的小模板函数,它将从堆中分配任意给定对象的副本,return 该副本:
template<typename T> inline T * CloneStaticObject(const T & item)
{
return new T(item);
}
适用于任何具有复制构造函数的类型...直到我传入一个引用到基础-class 类型,然后我遇到对象切片问题。
所以为了处理这个问题,我还有一个很好的 OOP 风格的小克隆界面,我可以使用它,完成一个 CloneDynamicObject(const ICloneable &)
函数,可以正确克隆从 [=15= 继承的任何对象],不管传入的引用是什么类型:
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);
}
...上面的实现几乎做了我想要的,除了它有两个问题:
dynamic_cast<>
不适用于所有类型- 即使如此,
dynamic_cast()
也会在 运行 时测试对象,如果可能的话,我想通过在编译时评估 test-for-ICloneability 来避免这种开销-时间代替。
我的问题是,有什么方法可以使用SFINAE来正确实现CloneAnyObject()
功能吗?
(请注意,我知道 SFINAE 技巧用于测试类型名称是否具有给定名称的方法,我认为这是一种非常有用的技术,但这并不是我在这里寻找的;我正在寻找的是一种测试类型是否从接口继承的方法)
您可以使用 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
来简化它,如
您可能有 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);
}
}