我有一个简单的模板结构,将字符串与值
相关联template<typename T> struct Field
{
std::string name; T self;
}
我有一个函数,我想接受任何类型的1个或多个字段,而字段可能有不同的类型,所以我使用的是std::initializer_list
因为C ++,据我所知,缺少类型化的可变参数,无法确定可变参数的大小,并且必须至少有一个其他参数来确定从哪里开始。
问题在于我不知道如何告诉它接受可能属于不同类型的字段。在Java中,我只使用foo(Field<?> bar, Field<?>... baz)
,但C ++缺少类型的可变参数和通配符。我唯一的另一个想法是制作类型的参数
std::initializer_list<Field<void*>>
,但这似乎是一个糟糕的解决方案......有更好的方法吗?
答案 0 :(得分:7)
有几件事......
C ++ 11(您在谈论std::initializer_list
时似乎已经拥有)确实有类型的可变参数,特别是它们被命名为可变参数模板
Java泛型和C ++模板是完全不同的野兽。 Java泛型创建一个存储对Object
的引用的单一类型,并提供对接口中类型的自动转换,但重要的是它执行类型擦除。
我建议你解释一下你想要解决的问题,并获得有关C ++中惯用问题的解决方案的建议。如果你想真正模仿Java中的行为(我不能坚持使用不同的语言并且有不同的习语),你可以在C ++中使用类型擦除手动(即使用boost::any
) 。但是我很少觉得程序中需要完全类型擦除...使用变体类型(boost::variant
)会更常见。
如果您的编译器支持可变参数模板(并非所有编译器都支持),您可以随时使用它,但存储字段以便稍后在向量中可能有点复杂,因为完全通用除非你使用类型擦除,否则接近。 (同样,要解决的问题是什么?可能有更简单的解决方案......)
答案 1 :(得分:4)
Java泛型更接近于将boost::any
填充到self
变量而不是C ++模板中。试一试。 C ++模板创建默认情况下彼此没有运行时或动态关系的类型。
你可以手动引入这样的关系,比如通过一个共同的父母,输入删除和明智地使用pImpl
和智能指针。
C类型的变量参数在C ++ 11中不合时宜。变量模板参数非常类型安全,只要您的编译器支持它们(2012年11月CTP for MSVC 2012支持它们(不是更新1,CTP),clang和非古老版本的gcc)。
C ++中的模板是一种元编程,更接近编写编写程序的程序而不是Java Generics。 Java Generic有一个共享的“二进制”实现,而C ++模板的每个实例都是一个完全不同的“程序”(通过像COMDAT折叠这样的过程,可以简化为一个二进制实现),其详细信息由模板描述代码。
template<typename T>
struct Field {
T data;
};
是一个小程序,上面写着“这里是如何创建字段类型”。传入int
和double
时,编译器会执行大致类似的操作:
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字节或源代码,然后以允许调试器连接回编写代码作为源代码的方式运行该代码的错误。
答案 2 :(得分:2)
在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...);
}
答案 3 :(得分:-1)
对于C ++来说,这不是很惯用。也许可以这样做; Coplien的书可能有一些想法。但是C ++是强类型的,因为它相信打字;试图将它变成Smalltalk或像雉鸡一样折叠它可能会导致眼泪。