【问题标题】:Common interface for derived classes using SFINAE使用 SFINAE 的派生类的通用接口
【发布时间】:2016-02-11 08:33:09
【问题描述】:

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

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

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

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

#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 类定义。我尝试定义一个纯虚拟IProperty::type(),但这当然不适用于模板派生类。

我有什么选择?

【问题讨论】:

  • 这不是 std::is_integral 和 std::is_same 雄心勃勃吗?

标签: c++ c++11 sfinae


【解决方案1】:

专业:

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

【讨论】:

  • 你不尊重is_xxxx的使用,允许输入任何类型
  • 由于不存在其他类型的特化,链接阶段对于像Property&lt;double&gt; 这样的东西会失败。根据设计,这实际上可能是需要的。但是,没有 SFINAE 的解决方案的行为与作者的解决方案不完全相同。它实际上更具限制性。
【解决方案2】:

扩展当前解决方案的一种简单方法确实是在基类中添加一个纯虚函数。为了让它编译,在派生类中添加一个额外的虚函数,它甚至可以和模板化的变体同名(虽然这可能会让人困惑):

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-&gt;type&lt;&gt;()

【讨论】:

  • 我喜欢这个解决方案,谢谢!只是一个问题,你能简要解释一下为什么会这样吗?为什么可能有virtual IProperty::type() 以及virtual Property::type()IProperty&lt;T&gt;::type()
  • 模板参数是函数签名的一部分。因此,type() 的模板化版本和非模板化版本的函数签名是不同的。 IProperty::type() 仅指 Property::type() (多态用法)。
【解决方案3】:
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&lt;T&gt;* 可以转换为IProperty*,因此可以访问getType 方法

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-12
    • 1970-01-01
    • 1970-01-01
    • 2021-08-29
    • 2021-02-15
    • 2019-04-16
    • 2014-11-26
    • 2010-12-12
    相关资源
    最近更新 更多