模板实例变量的C ++检查类型

时间:2020-09-23 16:15:19

标签: c++

我有与此question

相似的用例

我想检查参数中存储了哪种类型的实例变量而不会引发异常

class ParameterBase
{
public:
    virtual ~ParameterBase() {}
    template<class T> const T& get() const; //to be implimented after Parameter
    template<class T, class U> void setValue(const U& rhs); //to be implimented after Parameter
};

template <typename T>
class Parameter : public ParameterBase
{
public:
    Parameter(const T& rhs) :value(rhs) {}
    const T& get() const {return value;}
    void setValue(const T& rhs) {value=rhs;}    
private:
    T value;
};

//Here's the trick: dynamic_cast rather than virtual
template<class T> const T& ParameterBase::get() const
{ return dynamic_cast<const Parameter<T>&>(*this).get(); }
template<class T, class U> void ParameterBase::setValue(const U& rhs)
{ return dynamic_cast<Parameter<T>&>(*this).setValue(rhs); }

class Diagram
{
public:
    ParameterBase* v;
    int type;
};

我想要做的就是这样


if (diagram.getParameter().type == int) {
}


如何更改此实现,以便它可以让我窥视持有哪种类型的参数

感谢您的回答,还有几分

我使用的是C ++ 11,因此无法使用变体或任何

是否有执行此操作的标准方法。我想要的只是一个类的实例变量,该实例变量可以是多种类型(有界),在读取时检查其类型

2 个答案:

答案 0 :(得分:3)

简单修复

解决此问题的简单方法是向指针上的is<T>()定义的模板ParameterBase中添加模板函数dynamic_cast。带有指针的dynamic_cast在失败时会返回nullptr,这与引用会抛出std::bad_cast的引用不同。例如:

class ParameterBase
{
public:
    ...
    template <typename T>
    bool is() const;
};

...

template <typename T> 
bool ParameterBase::is() const
{ 
    return dynamic_cast<const Parameter<T>*>(this) != nullptr;
}

用途很简单:

if (diagram.getParameter().is<int>()) {
    ...
}

但是请注意,整个设计并不是特别好。它在基础和派生之间具有高度依赖的循环依赖性。另外,它需要ParameterBase作为指针存在才能正确操作;价值语义学将更加连贯(如果可能)

即使可以使用类型擦除,也可以使用类型擦除,这会更好(这是C ++ 17的Parameter会为您完成的工作)。您所链接问题中的second answer已经描述了外观。


类型删除的解决方案(

这使用了诸如转发引用,右值引用和std::any之类的C ++ 11功能-但该概念也可以应用于早期的C ++版本。

对于类型擦除,您需要一个至少包含以下两个功能的接口:

  • 获取对模板化类型的引用,并且
  • 获取当前类型的标识符。

由于C ++中的接口不能为unique_ptr,因此我们必须在返回引用方面颇具创意。 C ++具有virtual,可以是任何类型的指针。如果滥用(例如在错误的类型之间进行转换),这可能会很糟糕;但是如果我们知道底层类型,那就可以了。幸运的是,在这里,我们知道了底层类型。

可以通过以下方式来实现快速的类型擦除:

void*

在上面,我们自己承担检测基础类型的负担-但我们删除了循环耦合。现在,我们还有一个合适的值类型,可以像普通变量一样四处移动,这非常有帮助,因为它允许我们从API返回此对象,而不必担心生存期或所有权。

如果还需要可复制性,则可以扩展该接口以使其具有#include <type_traits> // std::decay #include <utility> // std::forward #include <typeinfo> // std::type_info, std::bad_cast #include <memory> // std::unique_ptr class Parameter { private: // This is the interface we will implement in all instances struct Interface { virtual ~Interface() = default; virtual void* get() = 0; virtual const std::type_info& type() const = 0; }; // This is the concrete instantiation of the above interfaces template <typename T> struct Concrete : public Interface { template <typename U> Concrete(U&& u) : m_value{std::forward<U>(u)} {} void* get() { return &m_value; } const std::type_info& type() const { return typeid(T); } T m_value; // actually holds the value here }; // This holds onto the interface, and only the interface std::unique_ptr<Interface> m_interface; public: // Constructs a parameter and sets the first interface value template <typename T> explicit Parameter(T&& value) : m_interface{new Concrete<typename std::decay<T>::type>{std::forward<T>(value)}} {} Parameter(Parameter&&) = default; Parameter& operator=(Parameter&&) = default; // Check if we are the same type by comparing the typeid template <typename T> bool is() const { return typeid(T) == m_interface->type(); } // Get the underlying value. Always check that we are the correct type first! template <typename T> const T& get() const { // do the error handling ourselves if (!is<T>()) { throw std::bad_cast{}; } // cast void* to the underlying T*. We know this is safe // because of our check above first return (*static_cast<T*>(m_interface->get())); } // Set the underlying value. Always check that we are the correct type first! template <typename T, typename U> void set(U&& value) { // do the error handling ourselves if (!is<T>()) { throw std::bad_cast{}; } (*static_cast<T*>(m_interface->get())) = std::forward<U>(value); } }; 功能或返回副本的功能

使用该对象,代码变为:

clone()

这里的工作量很小example


类型删除的解决方案(

如果您正在寻找一组有限的实例化,std::variant可以用于此目的。如果可能的基础类型的数量是 unbounded ,则应查看std::any

在任何一种情况下,这里的层次结构都是肤浅的(至少在当前示例中如此),因为可以将整个类型擦除减少为具有查询包含项能力的单数类型。以if (parameter.is<int>()) { /* treat parameter as an int */ } 为例,可以很容易地做到这一点:

std::any

如果您不希望更改活动成员,则可以通过首先检查#include <any> // std::any, std::any_cast class Parameter { public: // This implementation changes the active type if 'T' is not the same as the stored // value. If you want to restrict this, you can do error checking here instead. template <typename T> void set(const T& value) { m_value = value; } template <typename T> const T& get() { return std::any_cast<const T&>(m_value); } template <typename T> bool is() const noexcept { return m_value.type() == typeid(T); } private: std::any m_value; }; 并以某种方式处理错误来限制它。

只需执行以下操作即可查询活动类型:

is<T>()

如果类型是固定的,则始终可以使用if (parameter.is<int>()) { /* treat parameter as an int */ } 代替std::variant来定义std::has_alternative

答案 1 :(得分:0)

看起来像您预先知道了该参数的所有可能类型(我之所以这样说是因为您有一个type字段,期望将其用作枚举)。在这种情况下,您可以使用std::variant惯用语:

class Diagram
{
public:
    std::variant<Parameter<int>, Parameter<std::string>> v;
};

在这种情况下,您可以使用以下代码来了解实际类型:

switch(v.index()) {
case 0:
    // int is used
case 1:
    // string is used
}

请确保还有其他选择。例如,如果您有某种类型的东西,并且需要测试这是否是期望的类型,那么您可以使用std::is_same模板:

template <typename T>
class Parameter : public ParameterBase
{
public:
    bool isOfTypeInt() const {
        return std::is_same_v<T, int>;
    }
private:
    T value;
};