当前位置: 首页 > 知识库问答 >
问题:

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

云英才
2023-03-14

我有类似于这个问题用例

我想检查什么类型的实例变量存储在参数中而不引发异常

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所以不能使用variant或any

有没有标准的方法。我想要的是一个实例变量的类,可以是多种类型(有界的),并在阅读它时,检查它是什么类型

共有1个答案

施海
2023-03-14

解决问题的简单方法是将模板函数is () 添加到您的ParameterBase中,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作为指针存在,以便正确操作;其中值语义将更加连贯(如果可能的话)

如果您能够使用类型擦除就更好了,即使您定义了parameter(C++17的std::any将为您做这件事)。您的链接问题中的第二个答案已经描述了这可能是什么样子的。

这使用了C++11的功能,如转发引用、rvalue-references和unique_ptr,但这个概念也可以应用于早期的C++版本。

对于类型擦除,您需要一个至少包含以下2个特性的接口:

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

由于C++中的接口不能是virtual,所以我们必须在返回引用方面进行创新。C++有void*,它可以是任何类型的指针。如果使用不当(例如在错误的类型之间进行转换),这可能是不好的;但如果我们知道底层类型,就可以完美。谢天谢地,我们知道了底层类型。

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

};

在上面,我们自己承担检测底层类型的任务--但是我们去掉了循环耦合。我们现在也有了一个适当的值类型,我们可以像普通变量一样四处移动,这真的很有帮助,因为它允许我们从API返回这个对象,而不用担心生存期或所有权问题。

如果还需要可复制性,则可以扩展接口,使其具有clone()函数或返回副本的其他功能

使用此对象,代码变为:

if (parameter.is<int>()) {
    /* treat parameter as an int */
} 

这里有一个工作的小例子。

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

不管是哪种情况,在这里使用层次结构都是肤浅的(至少在当前示例中是这样),因为整个类型擦除可以被简化为具有查询包含能力的单数类型。使用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;
};

如果您不希望活动成员更改,可以通过首先检查 () 并以某种方式处理错误来限制这一点。

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

if (parameter.is<int>()) {
    /* treat parameter as an int */
} 

如果类型是固定的,则始终可以使用std::variant而不是使用std::has_alternative来定义is

 类似资料:
  • 我的函数使用一组给定的输入参数(变量)调用Python函数,并返回包含函数输出的元组(变量,因为输出随调用的函数而变化)。 我正在使用C 11通过MinGW-w64编译器的g端口在视窗10机器上编译。我声明了这个模板变量函数(并调用它)如下: 但是,会引发此错误(为了可读性,缩短为):

  • 变量通用属性 变量通用属性有title,value,type,tip,rule,message,除了通用属性个别变量还有其它属性,请看每个具体控件; "vars": { "varName1": { "title": "测试 text", /*后台设置时 input 的 label*/ "value": "1", /*变量默认值*/ "type

  • 变量通用属性 变量通用属性有title,value,type,tip,rule,message,除了通用属性个别变量还有其它属性,请看每个具体控件; "vars": { "varName1": { "title": "测试 text", /*后台设置时 input 的 label*/ "value": "1", /*变量默认值*/ "type

  • 我想实例化一个可变模板类

  • 我很难弄清楚如何使用适当的模板化参数调用setValue函数。在ParameterBase抽象基类中不可能有模板化的参数。非常感谢任何帮助。 附注。我没有使用boost::any的灵活性。