问题
I have a simple template struct associating a string with a value
template<typename T> struct Field
{
std::string name; T self;
}
I have a function that I want to accept 1-or-more Fields of any type, and the Fields may be of possible different types, so I'm using a std::initializer_list
because C++, to my knowledge, lacks typed variadic arguments, cannot determine the size of variadic arguments, and must have at least one other argument to determine where to start.
The problem is that I don't know how to tell it to accept Fields that may be of different types. In Java, I would just use foo(Field<?> bar, Field<?>... baz)
, but C++ lacks both typed variadic arguments and wildcards. My only other idea is to make the parameter of type
std::initializer_list<Field<void*>>
, but that seems like a bad solution... Is there a better way to do it?
回答1:
A couple of things...
C++11 (which you seem to have since you are talking about
std::initializer_list
) does have typed variadic arguments, in particular they are named variadic templatesJava generics and C++ templates are completely different beasts. Java generics create a single type that stores a reference to
Object
and provides automatic casting in and out to the types in the interface, but the important bit is that it performs type erasure.
I would recommend that you explain the problem you want to solve and get suggestions for solutions to your problem that are idiomatic in C++. If you want to really mimic the behavior in Java (which, I cannot insist enough is a different language and has different idioms) you can use type erasure in C++ manually (i.e. use boost::any
). But I have very rarely feel the need for full type erasure in a program... using a variant type (boost::variant
) is a bit more common.
If your compiler has support for variadic templates (not all compilers do), you can always play with that, but stashing the fields for later in a vector may be a bit complicated for a fully generic approach unless you use type erasure. (Again, what is the problem to solve? There might be simpler solutions...)
回答2:
Java generics are closer to just stuffing a boost::any
into the self
variable than to C++ templates. Give that a try. C++ templates create types that have no runtime or dynamic relarionship to each other by default.
You can introduce such a relationship manually, say via a common parent and type erasure and judicious use of pImpl
and smart pointers.
C type variardic arguments are out of style in C++11. Variardic template arguments are very type safe, so long as your compiler has support for them (Nov 2012 CTP for MSVC 2012 has support for them (not update 1, the CTP), as does clang, and non-ancient versions of gcc).
Templates in C++ is a kind of metaprogramming, closer to writing a program that writes a program than it is to Java Generics. A Java Generic has one shared "binary" implementation, while each instance of a C++ template is a completely different "program" (which, via procedures like COMDAT folding, can be reduced to one binary implementation), whose details are described by the template code.
template<typename T>
struct Field {
T data;
};
is a little program that says "here is how to create Field types". When you pass in an int
and double
, the compiler does something roughly like this:
struct Field__int__ {
int data;
};
struct Field__double__ {
double data;
};
and you wouldn't expect these two types to be convertible between.
Java generics, on the other hand, create something like this:
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()
};
where boost::any
is a container that can hold an instance of any type, and access to the data
field redirects through those accessors.
C++ provides means to write something equivalent to Java generics using template metaprogramming. To write something like C++ templates in Java, you'd have to have your Java program output custom Java byte or source code, then run that code in a way that allows a debugger to connect back to the code that writes the code as the source of the bugs.
回答3:
There is no need to use wildcards in C++ templates, since in C++ it always knows the type, and is not "erased" like in Java. To write void foo(Field<?> bar, Field<?>... baz)
method(or function) in C++, you would write:
template<class T, class... Ts>
void foo(Field<T> bar, Field<Ts>... baz);
Each Field<Ts>
can be a different type. To use the variadic parameters inside the function, you just use baz...
. So say you want to call another function:
template<class T, class... Ts>
void foo(Field<T> bar, Field<Ts>... baz)
{
foo2(baz...);
}
You can also expand the type with Field<Ts>...
, so if you want to put it in a tuple(you can't put them in array since they can be different types):
template<class T, class... Ts>
void foo(Field<T> bar, Field<Ts>... baz)
{
std::tuple<Field<Ts>...> data(baz...);
}
回答4:
This is not very idiomatic for C++. It can be done, perhaps; Coplien's book might have some ideas. But C++ is strongly typed because it believes in typing; trying to turn it into Smalltalk or fold it like a pheasant may lead to tears.
来源:https://stackoverflow.com/questions/15127137/templates-accepting-anything-in-c