【问题标题】:Templates accepting "anything" in C++在 C++ 中接受“任何东西”的模板
【发布时间】:2013-02-14 03:33:27
【问题描述】:

我有一个简单的模板结构,将字符串与值相关联

template<typename T> struct Field
{
    std::string name; T self;
}

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

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

【问题讨论】:

  • 具有不同模板参数的同一模板的实例化是完全不相关的类型。拥有Field 对象后,您打算如何处理它们? C++确实variadic templates(可用于创建类型安全的可变参数函数)。
  • 该函数只是将每个字段存储在一个向量中以备后用,所以我真的不在乎它们的类型是什么。 C++ 可变参数的类型安全性在这里确实是次要问题,因为如果在您发送错误类型时代码崩溃,那么就不要这样做(也就是说,在调用函数之前进行防御性代码并自己检查类型) .我知道 Field 和 Field 实际上并不相关(非(co/contra)方差使得不可能(嗯,不完全是,但由于非类模板参数而变得尴尬)。跨度>
  • @MattG 那么所有字段都进入 one 向量?在这种情况下,一个更困难的问题将是该向量的类型应该是什么。
  • “Java”解决方案的 C++ 版本是将 Field 定义为 struct Field{std::string name; boost::any self;};,但这可能是个坏主意,因为 C++ 允许比这更好的类型安全性。我可能要么让Field 模板继承自一个公共基类(具有具有所需接口的虚函数),然后使用value_ptr 来保存它们并传递它们, 使@ 987654331@ 是可能的字段类型的boost::variant
  • 嗯。我打算跟踪 std::tuple 中字段的类型,但大声说出来让我意识到这是多么荒谬。

标签: c++ c++11 templates variadic-templates


【解决方案1】:

有几件事...

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

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

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

如果您的编译器支持可变参数模板(并非所有编译器都支持),您可以随时使用它,但是隐藏向量中稍后的字段对于完全通用的可能有点复杂方法,除非您使用类型擦除。 (再次,要解决什么问题?可能有更简单的解决方案......)

【讨论】:

  • +1 表示“泛型和模板不同”,另一个 +1 表示“Java 和 C++ 有不同的习语”,另一个 +1 表示“要解决的潜在问题是什么”
  • @MarkB 2 sockpuppet 帐户? :p~
【解决方案2】:

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

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

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

C++ 中的模板是一种元编程,更接近于编写程序来编写程序,而不是 Java 泛型。 Java Generic 有一个共享的“二进制”实现,而 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 字节或源代码,然后以允许调试器连接回将代码作为源代码的方式运行该代码的错误。

【讨论】:

    【解决方案3】:

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

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

    每个Field&lt;Ts&gt; 可以是不同的类型。要在函数内部使用可变参数,只需使用baz...。所以说你想调用另一个函数:

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

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

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

    【讨论】:

    • std::tuple&lt;Ts...&gt; 怎么样?这可能吗?
    【解决方案4】:

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

    【讨论】:

    • “但是 C++ 是强类型的,因为它相信类型”——这里的问题不在于丢弃类型安全,而是 Java 等效项允许函数说“我不在乎参数化类型,因为我只处理具体已知的内容”(并且仍然让编译器强制执行它)
    猜你喜欢
    • 2015-11-10
    • 2018-07-06
    • 1970-01-01
    • 1970-01-01
    • 2017-06-03
    • 2022-01-19
    • 2017-09-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多