Variadic Templates
C++11 lets us define variadic templates, taking any amount of parameters, of any type, instead of just a specific number of parameters. Then, when we use the template, we can not only specify the types, we can specify an arbitrary amount of different types. For instance,
template <class... T_values> class Base { public virtual void something(T_values... values) = 0; }; class Derived1 : public Base<int, short, double> { public: void something(int a, short b, double c) override; }; class Derived2 : public Base<std::string, char> { public: void something(std::string a, char b) override; };
This is useful in extremely generic code such as libsigc++. We are gradually using this in libsigc++ and in glibmm, along with template aliases, to replace code that was previously generated by perl and python scripts to produce multiple versions of each C++ template, with various numbers of parameters. In the meantime, I’ve been playing with variadic templates in a separate experimental project and this is what I’ve learned along the way.
Parameter Packs
The “class… T_values” there is called a template parameter pack. If you are just templating a function then it’s called a function parameter pack:
class Thing { public: template <class... T_values> void something(T_values... values) = 0; };
Expanding a Parameter Pack
The “T… values” in that method signature is us expanding (or unpacking) the parameter pack in a function parameter list. You can use the … in various ways in the function parameter list. For instance, to receive the types as const references:
template <class... T_values> class Thing { public: void something(const T_values&... values) = 0; };
In your template, you can then call another method with that parameter pack, by expanding (or unpacking) it into the function argument list. For instance, with “values…”:
template <class... T_values> class Thing { public: void something(T_values... values) { something_else(values...); };
You can write more complex patterns to change how the parameter pack is expanded. For instance:
template <class... T_values> class Thing { public: void something(T_values... values) { something_else((values + 1)...); }; void other_thing(T_values... values) { something_else(const_cast<T>(values)...); };
Storing a Parameter Pack in a std::tuple
However, if you want to keep the parameter values around and use them at some later time, you’ll need to store them in a std::tuple. I think this is why std::tuple exists. For instance:
template <class... T_values> class Thing { public: void something(T_values... values) { tuple = std::tuple<T_values...>(values...); } private: std::tuple<T_values..> tuple_; };
Expanding a std::tuple
Then you have another problem. You probably want to call some method with those values. But now you have a tuple instead of a parameter pack. Trick with std::index_sequence<> and a helper method lets you call a normal method (that takes normal parameters), passing a tuple that holds those parameter values:
template <class... T_values> class Thing { public: void something(T_values... values) { tuple_ = std::tuple<T_values...>(values...); } void do_something_with_values() { call_yadda_with_tuple(tuple_, std::index_sequence_for<T_value...>()) } void yadda(T... values); private: //The helper method. template<std::size_t... Is> void call_yadda_with_tuple(const std::tuple<T_values...>& tuple, std::index_sequence<Is...>) { yadda(std::get<Is>(tuple)...); } std::tuple<T_values...> tuple_; };
Unfortunately, this does clutter up your code. I haven’t yet managed to write a simple generic call_function_with_tuple(f, tuple) helper method. I hope it is possible.
Parameter packs are part of the C++ language. std::tuple<> and std::index_sequence<> are part of the C++ standard library, apparently added specifically for use with parameter packs. I can see the sense in keeping the language as simple as possible, as long as you can provide what you need via library code in that language. But the end result is not very attractive in this case. Luckily, hopefully, this isn’t something you’ll need to use much anyway.
At this point, any reader who already doesn’t like C++’s complexity will like it even less. Showing them a related thousand-line g++ compilation error should turn them away for good (clang++’s compilation errors are much clearer). But if you really like compile-time type-safety, and if you really like to avoid copy/pasted code, you might like that this is at all possible.
It would be understandable for coding guidelines to discourage the use of variadic templates except in special cases, until people are more familiar with them.
Manipulating Tuples
Of course, you might need to call methods with just some of the parameters from the parameter pack, or some combination of parameter packs. Once you have the parameters in a std::tuple, you can manipulate that tuple with some more template cleverness. For instance:
- std::tuple_cat() concatenates two tuples into one.
- But I recently needed a tuple_cdr() to remove the first item from the tuple, leaving me the rest.
- A tuple_car() would just do std::get(tuple) to get the first time, so nobody bothers to write one.
- I even needed a tuple_interlace() to interlace two tuples together, turning a, b and c, d into a, c, b, d.
It all starts to feel very lispy. Luckily there are lots of people on StackOverflow who enjoy discussing how to implement these. I feel there should be more functions like std::tuple_cat() in the standard C++ library, or maybe in some open source library.
Once you have your new tuple, you’ll probably need to use std::make_index_sequence<> instead of std::index_sequence_for<>, to call your call_*_with_tuple() helper method, like so:
void do_something_more() { const auto combined_tuple = std::tuple_cat(tuple1_, tuple2_); constexpr auto tuple_size = std::tuple_size<decltype(combined_tuple)>::value; call_yadda_with_tuple(tuple, std::make_index_sequence<tuple_size>());}
> I haven’t yet managed to write a simple generic call_function_with_tuple(f, tuple) helper method. I hope it is possible.
The code for this is on cppreference under the second example
http://en.cppreference.com/w/cpp/utility/integer_sequence
Thanks. That’s useful, though I think it’s still lengthy if we are calling a member method, or when we want to pass normal parameters too.
shouldn’t
“template ” be “template ” ?
You use T later but define T_values.
You don’t need this helper method.
std::tie can pack and unpack tuples
Thanks. I’ve corrected the uses of T to T_values.
I think that std::tie() would only be useful when calling a non-variadic method, and probably only when calling a non-templated method, because it needs me to know the number and types of the parameters.
For instance:
int a = 0;
double b = 0;
std::string c;
std::tie(a, b, c) = values_tuple;
some_method(a, b, c);
I don’t see any way to make that generic.
std::tie is more useful with functions that use tuples to simulate multiple return values. It essentially lets the caller hide all the tuple maipulation with something like:
tie(ret1, ret2, ret3) = f();
The following is not possible in C++. You cannot virtualize a template method.
template
virtual void something(const T_values&… values) = 0;
};
You can do that as lng as the template parameters are part of the class and not the functiin itself.
The example did show a virtual templated member function not an ordinary virtual member function of a class template – and that is impossible.
Thanks. The virtual was useless, from copy/pasting the earlier code. I’ve corrected it now.
You probably want to take a look at Boost.Hana: https://github.com/boostorg/hana
It provides several algorithms on tuples and other metaprogramming tools.
Yes, thanks. I’ll definitely explore hana. As a start, I’ve just watched your CppCon 2015 talk
https://www.youtube.com/watch?v=cg1wOINjV9U
and found it rather mind expanding. Thanks.
http://en.cppreference.com/w/cpp/experimental/apply
Thanks. So, something like this?:
auto result = std::experimental::apply(
[this, some_arg] (T_value_types... the_values) {
return this->do_something(some_arg, the_values...);
},
values_tuple);
That does inded avoid me having to write a helper method, though I seem to need a parameter pack in the lamda’s signature. If so, this wouldn’t help me when I want to call a method with just some of the parameters, for instance after removing the first element of the tuple.
Your ‘virtual functions’ still appear to be completely nonsensical, as you are overriding one abstract base with no known signature, with 2 different deriveds that have contradictory signatures. Even if this worked, it wouldn’t be polymorphic, so there’d be precisely no point in having the virtual keyword in there. What is this meant to achieve?
Thanks, but the signatures of the methods in the Base<> template instantiation seem to be entirely known.
In the first example (the only one involving the virtual keyword)
Base
would have a:
virtual void something(int, short, double);
And that’s exactly what Derived1’s something() overrides.
Dervied2 derives from a different Base<> template instantiation and therefore has a different something() to override.
Could you explain where you see the problem with that?
Thanks for your reply. So, I guess the example is oversimplified and in reality you’d have Derived1A, Derived1B, Derived1C etc – and use polymorphism on those? Otherwise I just can’t (couldn’t) see a purpose for the virtual specifier.
I stress that you clearly know way more than me, so it’s prob just a fault in my understanding, i.e. taking an abridged example far too literally. If so, my bad!
Btw, sorry if I seemed terse. GTKmm is a revelation: just what I need (all the features of GTK+, no bare verbosity of C, & lately more revelations of C++11), and from it I’ve come to much enjoy your expert posts here and on SO, etc.
The something() part of the APi is polymorphic, yes. In my own full code, it lets the Derived something() provide implementation to be used by generalised code in base.
It’s very possibly not the best example. It’s just one that I found useful and solved a real problem. I’ve been meaning to blog about the little toy project where I used exactly this technique:
https://github.com/murraycu/murrayc-dp-algorithms
Thanks for the kind words.