C ++ 11索引模板参数包在运行时为了访问第N个类型 [英] C++11 indexing template parameter packs at runtime in order to access Nth type

查看:100
本文介绍了C ++ 11索引模板参数包在运行时为了访问第N个类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从此 SO主题(以及此博客帖子),我知道如何访问模板参数包中的第N个类型。例如,上述SO问题的答案之一提示:

From this SO topic (and this blog post), I know how to access Nth type in a template parameter pack. For instance, one of the answers to the abovementioned SO question suggests this:

template<int N, typename... Ts> using NthTypeOf = typename std::tuple_element<N, std::tuple<Ts...>>::type;

using ThirdType = NthTypeOf<2, Ts...>;

但是,这些方法仅在编译时有效。尝试执行以下操作:

However, these methods work only in compile-time. Trying to do something such as:

int argumentNumber = 2;
using ItsType = NthTypeOf<argumentNumber, Arguments...>;

会导致编译错误:


错误:非类型模板参数不是常量表达式

Error : non-type template argument is not a constant expression

是否可以访问第N个类型在运行时?

Is there a way to access Nth type at runtime?

这是我的用例:

我的程序读取一个文本文件,该文件基本上是一个数字数组。每个数字 i 都指的是基于类的模板参数包的第 i 个类型。基于该类型,我想声明该类型的变量并对它做一些不同的事情。例如,如果是字符串,则要声明一个字符串并进行字符串匹配;如果是整数,则要计算数字的平方根。

My program reads a text file, which is basically an array of numbers. Each number i refers to the i-th type of a template parameter pack that my class is templated based on. Based on that type, I want to declare a variable of that type and do something differently with it. For example, if it's a string, I want to declare a string and do string matching, and if it's an integer, I would like to compute the square root of a number.

推荐答案

C ++是一种静态类型的语言。因此,所有变量的类型都需要在编译时知道(并且不能改变)。您需要一种取决于运行时值的类型。幸运的是,C ++还具有动态类型的对象类型。

C++ is a statically​ typed language. As such the type of all variables needs to be known at compile time (and cannot vary). You want a type that depends on a runtime value. Luckily C++ also features dynamic typing of objects.

警告:此答案中的所有代码仅提供服务用于演示基本概念/想法。它缺少任何类型的错误处理,合理的接口(构造函数...),异常安全性...。因此,不要用于生产,请考虑使用boost提供的实现。

要使用此功能,您需要称为多态基数的class :具有(至少)一个 virtual 成员函数的类,您可以从中导出其他类。

To use this feature you need what's called a polymorphic base class: a class with (at least) one virtual member function from which you derive further classes.

struct value_base {
  // you want to be able to make copies
  virtual std::unique_ptr<value_base> copy_me() const = 0;
  virtual ~value_base () {}
};

template<typename Value_Type>
struct value_of : value_base {
  Value_Type value;

  std::unique_ptr<value_base> copy_me() const {
    return new value_of {value};
  }
};

然后可以使用静态类型的指针或对该基类的引用的变量从基类以及任何这些派生类中引用/引用对象。如果您有一个定义明确的接口,则将其编码为虚拟成员函数(请考虑 Shape area() name(),...函数)并通过该基类指针/引用进行调用(如其他答案所示)。否则,请使用(隐藏的)动态类型转换获取具有动态类型的静态类型的指针/引用:

You can then have a variable with static type of pointer or reference to that base class, which can point to/reference objects from both the base class as well as from any of those derived classes. If you have a clearly defined interface, then encode that as virtual member functions (think of Shape and area (), name (), ... functions) and make calls through that base class pointer/reference (as shown in the other answer). Otherwise use a (hidden) dynamic cast to obtain a pointer/reference with static type of the dynamic type:

struct any {
  std:: unique_ptr<value_base> value_container;

  // Add constructor

  any(any const & a)
    : value_container (a.value_container->copy_me ())
  {}
  // Move constructor

  template<typename T>
  T & get() {
    value_of<T> * typed_container
        = dynamic_cast<value_of<T> *>(value_container.get();)
    if (typed_container == nullptr) {
      // Stores another type, handle failure
    }
    return typed_container->value;
  }

  // T const & get() const;
  // with same content as above
};

template<typename T, typename... Args>
any make_any (Args... && args) {
  // Raw new, not good, add proper exception handling like make_unique (C++14?)
  return {new T(std:: forward<Args>(args)...)};
}

由于对象是在运行时完成的,所以指向/引用的对象的实际类型对象可能取决于运行时值:

Since object construction is done at runtime the actual type of the pointed to/referenced object may depend on runtime values:

template<typename T>
any read_and_construct (std:: istream & in) {
  T value;
  // Add error handling please
  in >> value;
  return make_any<T>(std:: move (value));
}

// ...

// missing: way of error handling
std::map<int, std:: function<any(std:: istream &)>> construction_map;
construction_map.insert(std::make_pair(1, read_and_construct<double>));
// and more
int integer_encoded_type;
// error handling please
cin >> integer_encoded_type;
// error handling please
any value = construction_map [integer_encoded_type] (cin);

您可能已经注意到,上面的代码还使用了一个明确定义的构造接口。 如果您不愿意,打算对返回的任何对象做很多不同的事情,有可能将它们存储在大部分对象的各种数据结构中当程序运行时,使用任何类型的 then 很有可能过大了,您也应该将类型相关的代码放入这些构造函数中

As you may have noticed above code uses also a clearly defined interface for construction. If you don't intend to do lots of different things with the returned any objects, potentially storing them in various data structures over great parts of the time your program is running, then using an any type is most likely overkill and you should just put the type dependent code into those construction functions, too.

这种 any 类的一个严重缺点是它的通用性:可以在其中存储几乎任何类型它。这意味着在编译期间(实际)存储的对象的(最大)大小是未知的,这使得使用具有自动持续时间的存储(堆栈)成为不可能(在标准C ++中)。这可能导致动态内存(堆)的昂贵使用,这比自动内存要慢很多。每当必须制作许多 any 对象的副本时,此问题就会浮出水面,但如果仅保留它们的集合,则可能无关紧要(缓存局部性除外)。

A serious drawback of such an any class is its generality: it's possible to store just about any type within it. This means that the (maximum) size of the (actually) stored object is not known during compilation, making use of storage with automatic duration (the "stack") impossible (in standard C++). This may lead to expensive usage of dynamic memory (the "heap"), which is considerably slower than automatic memory. This issue will surface whenever many copies of any objects have to be made, but is probably irrelevant (except for cache locality) if you just keep a collection of them around.

因此,如果您在编译时知道必须存储的类型集,则可以(在编译时)计算所需的最大大小,请使用该大小的静态数组,然后在该数组中构造对象(由于C ++ 11,您可以使用(递归模板) union 实现相同的目的,

Thus, if you know at compile time the set of types which you must be able to store, then you can (at compile time) compute the maximum size needed, use a static array of that size and construct your objects inside that array (since C++11 you can achieve the same with a (recursive template) union, too):

constexpr size_t max_two (size_t a, size_t b) {
  return (a > b) ? a : b;
}

template<size_t size, size_t... sizes>
constexpr size_t max_of() {
  return max_two (size, max_of<sizes>());
}

template<typename... Types>
struct variant {
  alignas(value_of<Types>...) char buffer[max_of<sizeof (value_of<Types>)...>()];
  value_base * active;

  // Construct an empty variant
  variant () : active (nullptr)
  {}

  // Copy and move constructor still missing!

  ~variant() {
    if (active) {
      active->~value_base ();
    }
  }

  template<typename T, typename... Args>
  void emplace (Args... && args) {
    if (active) {
      active->~value_base ();
    }
    active = new (buffer) T(std:: forward<Args>(args)...);
  }
};

这篇关于C ++ 11索引模板参数包在运行时为了访问第N个类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆