8

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?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Matt G
  • 1,661
  • 19
  • 32
  • 2
    Instantiations of the same template with different template arguments are totally unrelated types. What do you plan to do with the `Field` objects once you have them? C++ *does* have [variadic templates](http://en.wikipedia.org/wiki/Variadic_template) (which can be used to create typesafe variadic functions). – Mankarse Feb 28 '13 at 03:56
  • the function is only going to stash each of the fields in a vector for later, so I really don't care what their types _are_. The type-safety of C++'s variadics is really a secondary concern here, because if the code crashes when you send in bad types, well don't do that then (that is, code defensively and check the types yourself before you call the function). I know that the Field and Field aren't actually related (non-(co/contra)-variance made impossible (well, not exactly, but made awkward by) non-class template parameters). – Matt G Feb 28 '13 at 04:05
  • @MattG So the fields all go into _one_ vector? In that case an even more difficult question will be what the type of that vector should be. – jogojapan Feb 28 '13 at 04:09
  • 2
    The C++ version of the "Java" solution would be to define `Field` as `struct Field{std::string name; boost::any self;};`, but this is probably a bad idea, as C++ allows for better type-safety than that. I would probably either make the `Field` template inherit from a common base class (with virtual functions with the required interface) and then use a [value_ptr](https://bitbucket.org/martinhofernandes/wheels/src/a3365a24524e/include/wheels/smart_ptr/value_ptr.h%2B%2B) to hold them and pass them around, **or** make `self` be a `boost::variant` of the possible field types. – Mankarse Feb 28 '13 at 04:11
  • Hm. I was going to keep track of the types of the fields in a std::tuple, but saying it out loud makes me realize how ridiculous that is. – Matt G Feb 28 '13 at 04:15
  • @MattG: If you were considering using a tuple to track the types, why not use a tuple directly to store the values with full type safety? – David Rodríguez - dribeas Feb 28 '13 at 04:48
  • Because I... forgot that tuples stored more than just type information. Uh. This is awkward. – Matt G Feb 28 '13 at 06:42
  • @Mankarse: Damn Martinho! I had something like this around (for Pimpl implementation), but this version is utter madness! – Matthieu M. Feb 28 '13 at 07:33
  • Storing the values is the easy bit, the real question is reading them out again. Do you have a clear idea how you wish to use this data? That might help us with a good answer. – Aaron McDaid Jan 21 '15 at 18:10

4 Answers4

9

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 templates

  • Java 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...)

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
4

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
2

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...);
 }
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
-1

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.

Mark Bernstein
  • 2,090
  • 18
  • 23
  • "But C++ is strongly typed because it believes in typing" -- the issue here isn't with discarding type safety, but rather the Java equivalent allowing a function to say "I don't care about the parameterized type because I only deal with what is concretely known" (and still letting the compiler enforce it) – Del Oct 15 '18 at 19:29