接受C++"anything"的模板

Templates accepting "anything" in C++

本文关键字:anything C++ 接受      更新时间:2023-10-16

我有一个简单的模板结构体将字符串与值

关联起来
template<typename T> struct Field
{
    std::string name; T self;
}

我有一个函数,我想接受任何类型的1个或多个字段,并且字段可能是不同的类型,所以我使用std::initializer_list,因为据我所知,c++缺乏类型化可变参数,无法确定可变参数的大小,并且必须至少有一个其他参数来确定从哪里开始。

问题是我不知道如何告诉它接受可能是不同类型的字段。在Java中,我只会使用foo(Field<?> bar, Field<?>... baz),但是c++缺少类型可变参数和通配符。我唯一的另一个想法是将参数设为typestd::initializer_list<Field<void*>>,但这似乎是一个糟糕的解决方案…有更好的方法吗?

有几件事…

  • c++ 11(你似乎有,因为你正在谈论std::initializer_list)确实有类型可变参数,特别是它们被命名为可变模板

  • Java泛型和c++模板是完全不同的东西。Java泛型创建一个单一类型,该类型存储对Object的引用,并提供对接口中类型的自动转换,但重要的是它执行类型擦除。

我建议你解释你想要解决的问题,并获得c++惯用的解决方案建议。如果你想真正模仿Java中的行为(我不能坚持Java是一种不同的语言,有不同的习惯用法),你可以在c++中手动使用类型擦除(即使用boost::any)。但是我很少觉得在程序中需要完全类型擦除……使用变体类型(boost::variant)比较常见。

如果你的编译器支持可变模板(不是所有的编译器都支持),你总是可以使用它,但是在vector中存储字段对于一个完全泛型的方法来说可能有点复杂,除非你使用类型擦除。(同样,要解决的问题是什么?可能有更简单的解决方案…)

Java泛型更接近于将boost::any填充到self变量中,而不是c++模板。试试吧。c++模板创建的类型在默认情况下彼此之间没有运行时或动态关系。

您可以手动引入这种关系,例如通过共同的父类和类型擦除以及明智地使用pImpl和智能指针。

C类型变参数在c++ 11中过时了。可变模板参数是非常类型安全的,只要你的编译器支持它们(2012年11月的MSVC 2012 CTP支持它们(不是更新1,CTP), clang和非旧版本的gcc也支持它们)。

c++中的

模板是一种元编程,更接近于编写一个编写程序的程序,而不是Java泛型。Java泛型有一个共享的"二进制"实现,而c++模板的每个实例是一个完全不同的"程序"(通过类似COMDAT折叠的过程,可以简化为一个二进制实现),其细节由模板代码描述。

 template<typename T>
 struct Field {
   T data;
 };

是一个小程序,它说"这里是如何创建字段类型"。当传入intdouble时,编译器大致做如下操作:

 struct Field__int__ {
   int data;
 };
 struct Field__double__ {
   double data;
 };

,你不会期望这两种类型之间可以转换。

另一方面,Java泛型创建了如下内容:
struct Field {
  boost::any __data__;
  template<typename T>
  T __get_data() {
    __data__.get<T>();
  }
  template<typename T>
  void __set_data(T& t) {
    __data__.set(t);
  }
  property data; // reading uses __get_data(), writing uses __set_data()
};

其中boost::any是一个可以容纳任何类型实例的容器,并且对data字段的访问通过这些访问器进行重定向。

c++提供了使用模板元编程编写与Java泛型等价的东西的方法。要在Java中编写类似c++模板的东西,您必须让Java程序输出自定义Java字节或源代码,然后以允许调试器连接回编写代码的代码的方式运行该代码,作为错误的来源。

在c++模板中不需要使用通配符,因为在c++中它总是知道类型,并且不会像Java中那样被"擦除"。要在c++中编写void foo(Field<?> bar, Field<?>... baz)方法(或函数),您可以这样写:

 template<class T, class... Ts>
 void foo(Field<T> bar, Field<Ts>... baz);

每个Field<Ts>可以是不同的类型。要在函数中使用可变参数,只需使用baz...。假设你想调用另一个函数:

 template<class T, class... Ts>
 void foo(Field<T> bar, Field<Ts>... baz)
 {
     foo2(baz...);
 }

你也可以用Field<Ts>...扩展类型,所以如果你想把它放在一个元组中(你不能把它们放在数组中,因为它们可以是不同的类型):

 template<class T, class... Ts>
 void foo(Field<T> bar, Field<Ts>... baz)
 {
     std::tuple<Field<Ts>...> data(baz...);
 }

这对于c++来说不是很习惯。也许这是可以做到的;Coplien的书里可能有一些想法。但是c++是强类型的,因为它相信类型;试图把它变成Smalltalk或像野鸡一样折叠可能会导致眼泪。