使用 SFINAE 派生 类 的通用接口

Common interface for derived classes using SFINAE

我想向 C++ class 添加(动态)属性,它可以是多种类型(例如 floatintbool)。根据它们的值类型,界面中应该显示不同的控件。

为此,我使用 SFINAE 为 type() 函数创建了一个简单的 属性 class:

#include <iostream>
#include <type_traits>

template <class T>
class Property
{
public:
  enum Type {
    Undefined = -1,
    Int,
    Float,
    Bool,
  };

  explicit Property(const std::string& name) : name_(name) { }

  const std::string& name() const { return name_; }

  // specialization for floating point type() getter
  template<class U = T,
           typename std::enable_if<std::is_floating_point<U>::value>::type* = nullptr>
  Type type() const {
    return Type::Float;
  }

  // specialization for integer type() getter
  template<class U = T,
           typename std::enable_if<std::is_integral<U>::value>::type* = nullptr>
  Type type() const {
    return Type::Int;
  }

  // specialization for boolean type() getter
  template<class U = T,
           typename std::enable_if<std::is_same<U, bool>::value>::type* = nullptr>
  Type type() const {
    return Type::Bool;
  }

private:
  std::string name_;
  T value_;
};

int main() {
  // this works
  auto fProp = new Property<float>("float property");
  std::cout << fProp->type() << std::endl;
}

到目前为止,这工作得相当好。现在,当我想将这些属性中的几个存储在一个向量中时,问题就来了。为此,我创建了一个通用接口,并相应地更改了 class:

#include <iostream>
#include <type_traits>
#include <vector>

class IProperty
{
  // common interface for all typed Property<T>'s
public:
  enum Type {
    Undefined = -1,
    Int,
    Float,
    Bool,
  };

  virtual const std::string& name() const = 0;
};

template <class T>
class Property : public IProperty
{
public:
  explicit Property(const std::string& name) : name_(name) { }

  const std::string& name() const { return name_; }

  // specialization for floating point type() getter
  template<class U = T,
           typename std::enable_if<std::is_floating_point<U>::value>::type* = nullptr>
  Type type() const {
    return Type::Float;
  }

  // specialization for integer type() getter
  template<class U = T,
           typename std::enable_if<std::is_integral<U>::value>::type* = nullptr>
  Type type() const {
    return Type::Int;
  }

  // specialization for boolean type() getter
  template<class U = T,
           typename std::enable_if<std::is_same<U, bool>::value>::type* = nullptr>
  Type type() const {
    return Type::Bool;
  }

private:
  std::string name_;
  T value_;
};

int main() {

  // works
  auto fProp = new Property<float>("float property");
  std::cout << fProp->type() << std::endl;

  std::vector<IProperty*> properties;
  properties.push_back(fProp);

  // error: 'class IProperty' has no member named 'type'
  for (auto iprop : properties) {
    std::cout << iprop->type() << std::endl;  
  }

}

如您所见,我无法调用 type() 方法,因为它没有为 IProperty class 定义。我尝试定义一个纯虚拟 IProperty::type(),但这当然不适用于模板派生的 class.

我有哪些选择?

class IProperty 
{ 
public:
  virtual Type getType() const=0; 
  enum {...}
};

template < class T > class PropertyByType : public IProperty
{
  // implement here the differents type() method 

  // then :
  virtual Type getType(){ return type<T>();}
}

template < class T >
class Property : public PropertyByType<T>
{
  // ...
}

现在 Property<T>* 可以转换为 IProperty*,因此可以使用 getType 方法

专业化:

class IProperty
{
  // common interface for all typed Property<T>'s
public:
  enum Type {
    Undefined = -1,
    Int,
    Float,
    Bool,
  };

  virtual const std::string& name() const = 0;
  virtual Type type() const { return Type::Undefined }
};

template <class T>
class Property : public IProperty
{
public:
  explicit Property(const std::string& name) : name_(name) { }

  const std::string& name() const override { return name_; }

  Type type() const override;

private:
  std::string name_;
  T value_;
};

template <> Type Property<float>::type() const { return Type::Float;}
template <> Type Property<int>::type() const { return Type::Int;}
template <> Type Property<bool>::type() const { return Type::Bool;}

扩展当前解决方案的一个简单方法确实是在基础 class 中添加一个纯虚函数。为了使其编译,在派生的 class 中添加一个额外的虚函数,它甚至可以与模板化变体具有相同的名称(尽管它可能会造成混淆):

class IProperty
{
    // ...
    virtual Type type() const = 0;
};

template <class T>
class Property : public IProperty
{
public:
    // ...

    // here type() is not a template, so it can be virtual
    virtual Type type() const override
    {
        return type<T>();
    }
};

现在,如果你使用像

这样的东西
auto fProp = new Property<float>("float property");
std::cout << fProp->type() << std::endl;

它实际上会调用新的接口函数而不是模板函数。

要直接调用模板,必须明确说明:

auto fProp = new Property<float>("float property");
// note the <>
std::cout << fProp->type<>() << std::endl;

您实际上可能希望将内部(模板化)type() 函数声明为私有,因为它们不是接口 (IPorperty) 的一部分,不应公开。这将禁止调用 fProp->type<>().