31

So I have some type X:

typedef ... X; 

and a template function f:

class <typename T> void f(X& x_out, const T& arg_in); 

and then a function g:

void g(const X* x_array, size_t x_array_size); 

I need to write a variadic template function h that does this:

template<typename... Args> void h(Args... args) { constexpr size_t nargs = sizeof...(args); // get number of args X x_array[nargs]; // create X array of that size for (int i = 0; i < nargs; i++) // foreach arg f(x_array[i], args[i]); // call f (doesn't work) g(x_array, nargs); // call g with x_array } 

The reason it doesn't work is because you can't subscript args like that at runtime.

What is the best technique to replace the middle part of h?

And the winner is Xeo:

template<class T> X fv(const T& t) { X x; f(x,t); return x; } template<class... Args> void h(Args... args) { X x_array[] = { fv(args)... }; g(x_array, sizeof...(Args)); } 

(Actually in my specific case I can rewrite f to return x by value rather than as an out parameter, so I don't even need fv above)

4
  • args isn't an array -- what were you expecting? Commented Aug 19, 2012 at 23:16
  • 5
    @KerrekSB: Sure. I wasn't expecting it to work, just using it as pseudo-code to describe the problem. Commented Aug 19, 2012 at 23:17
  • Since you're using C++11, can't you make h take an initializer list and work with vectors instead of arrays? Commented Aug 19, 2012 at 23:22
  • 5
    @jrok: The types of the parameters to h are heterogeneous. I think initializer_list is for homogeneous parameters. Commented Aug 19, 2012 at 23:29

5 Answers 5

32

You could refactor or wrap f to return a new X instead of having it passed, since this would play pack expansion into the hand and make the function really concise:

template<class T> X fw(T const& t){ X x; f(x, t); return x; } template<class... Args> void h(Args... args){ X xs[] = { fw(args)... }; g(xs, sizeof...(Args)); } 

Live example.

And if you could change g to just accept an std::initializer_list, it would get even more concise:

template<class... Args> void h(Args... args){ g({f(args)...}); } 

Live example. Or (maybe better), you could also provide just a wrapper g that forwards to the real g:

void g(X const*, unsigned){} void g(std::initializer_list<X> const& xs){ g(xs.begin(), xs.size()); } template<class... Args> void h(Args... args){ g({f(args)...}); } 

Live example.
Edit: Another option is using a temporary array:

template<class T> using Alias = T; template<class T> T& as_lvalue(T&& v){ return v; } template<class... Args> void h(Args... args){ g(as_lvalue(Alias<X[]>{f(args)...}), sizeof...(Args)); } 

Live example. Note that the as_lvalue function is dangerous, the array still only lives until the end of the full expression (in this case g), so be cautious when using it. The Alias is needed since just X[]{ ... } is not allowed due to the language grammar.

If all of that's not possible, you'll need recursion to access all elements of the args pack.

#include <tuple> template<unsigned> struct uint_{}; // compile-time integer for "iteration" template<unsigned N, class Tuple> void h_helper(X (&)[N], Tuple const&, uint_<N>){} template<unsigned N, class Tuple, unsigned I = 0> void h_helper(X (&xs)[N], Tuple const& args, uint_<I> = {}){ f(xs[I], std::get<I>(args)); h_helper(xs, args, uint_<I+1>()); } template<typename... Args> void h(Args... args) { static constexpr unsigned nargs = sizeof...(Args); X xs[nargs]; h_helper(xs, std::tie(args...)); g(xs, nargs); } 

Live example.

Edit: Inspired by ecatmur's comment, I employed the indices trick to make it work with just pack expansion and with f and g as-is, without altering them.

template<unsigned... Indices> struct indices{ using next = indices<Indices..., sizeof...(Indices)>; }; template<unsigned N> struct build_indices{ using type = typename build_indices<N-1>::type::next; }; template <> struct build_indices<0>{ using type = indices<>; }; template<unsigned N> using IndicesFor = typename build_indices<N>::type; template<unsigned N, unsigned... Is, class... Args> void f_them_all(X (&xs)[N], indices<Is...>, Args... args){ int unused[] = {(f(xs[Is], args), 1)...}; (void)unused; } template<class... Args> void h(Args... args){ static constexpr unsigned nargs = sizeof...(Args); X xs[nargs]; f_them_all(xs, IndicesFor<nargs>(), args...); g(xs, nargs); } 

Live example.

Sign up to request clarification or add additional context in comments.

13 Comments

X xs[] = { f(args)... }; is pretty cool. Actually the specific case of my problem is MySQL's MYSQL_BIND (= X) structure and I am not sure if it can be copied. But if it can that's very nice.
Added live examples to all code snippets, fixed several typos and provided a wrapper option for g. Hope something of it does help with your MYSQL stuff. :)
Actually if you look at code sample here: dev.mysql.com/doc/refman/5.5/en/mysql-stmt-execute.html. The MYSQL_BIND struct can be copied so I think your solution will work.
In your first sample I think you can discard length_of and just call sizeof... again, no?
@Andrew: Yeah, it's a leftover from refactoring. At first, I just had X xs[], but I needed to change that to X xs[sizeof...(Args)] because the former wouldn't match T(&)[N] for whatever reason. However, adding the array size explicitly kinda voids the need for length_of itself, so yeah... :P
|
9

Nice template as answer for first part of question:

template <class F, class... Args> void for_each_argument(F f, Args&&... args) { [](...){}((f(std::forward<Args>(args)), 0)...); } 

2 Comments

Quite an interesting method of simulating fold expressions.
This solution, although pretty, does not guarantee evaluation order. See: godbolt.org/g/2KiZCD
6

It's obvious: you don't use iteration but recursion. When dealing with variadic templates something recursive always comes in. Even when binding the elements to a std::tuple<...> using tie() it is recursive: It just happens that the recursive business is done by the tuple. In your case, it seems you want something like this (there are probably a few typos but overall this should work):

template <int Index, int Size> void h_aux(X (&)[Size]) { } template <int Index, int Size, typename Arg, typename... Args> void h_aux(X (&xs)[Size], Arg arg, Args... args) { f(xs[Index], arg); h_aux<Index + 1, Size>(xs, args...); } template <typename... Args> void h(Args... args) { X xs[sizeof...(args)]; h_aux<0, sizeof...(args)>(xs, args...); g(xs, sizeof...(args)); } 

I think you won't be able to use nargs to define the size of the array either: Nothing indicates to the compiler that it should be a constant expression.

7 Comments

I have gcc extensions on so I think it can handle dynamicly sized array like that. But if you didn't you could put sizeof... directly into the array size as you have done.
@Andrew: You shouldn't rely on compiler extensions.
@Xeo: It depends on the portability requirements of your project. A lot of the extensions are very useful, so it makes more sense to use them if you are targeting only a single platform.
@Andrew: Even then you shouldn't, requirements can change, as can support for extensions.
I think this forum is about C++ not about C++ with extensions. Useful code is bound to be ported to a different environment and the use of extensions can get into the way. Since support for variable sized arrays isn't really necessary in this context, I'd think it is best avoided. The code sample I posted avoids the problem by always using sizeof...() but using constexpr is another alternative. In any case, using nargs wouldn't work as template argument unless it is made a constant expression.
|
4

It's fairly simple to do with parameter pack expansion, even if you can't rewrite f to return the output parameter by value:

struct pass { template<typename ...T> pass(T...) {} }; template<typename... Args> void h(Args... args) { const size_t nargs = sizeof...(args); // get number of args X x_array[nargs]; // create X array of that size X *x = x_array; int unused[]{(f(*x++, args), 1)...}; // call f pass{unused}; g(x_array, nargs); // call g with x_array } 

It should be possible just to write

 pass{(f(*x++, args), 1)...}; // call f 

but it appears g++ (4.7.1 at least) has a bug where it fails to order the evaluation of brace-initializer-list parameters as class initialisers. Array initialisers are OK though; see Sequencing among a variadic expansion for more information and examples.

Live example.


As an alternative, here's the technique mentioned by Xeo using a generated index pack; unfortunately it does require an extra function call and parameter, but it is reasonably elegant (especially if you happen to have an index pack generator lying around):

template<int... I> struct index { template<int n> using append = index<I..., n>; }; template<int N> struct make_index { typedef typename make_index<N - 1>::type::template append<N - 1> type; }; template<> struct make_index<0> { typedef index<> type; }; template<int N> using indexer = typename make_index<N>::type; template<typename... Args, int... i> void h2(index<i...>, Args... args) { const size_t nargs = sizeof...(args); // get number of args X x_array[nargs]; // create X array of that size pass{(f(x_array[i], args), 1)...}; // call f g(x_array, nargs); // call g with x_array } template<typename... Args> void h(Args... args) { h2(indexer<sizeof...(args)>(), std::forward<Args>(args)...); } 

See C++11: I can go from multiple args to tuple, but can I go from tuple to multiple args? for more information. Live example.

3 Comments

Is the parameter pack guaranteed to be evaluated in order? I seem to remember that it isn't, but I could be misremembering.
@AndrewTomazos-Fathomling parameter packs don't have any special evaluation order; here I'm using a brace-enclosed list which is guaranteed to be evaluated left-right (8.5.4:4).
You don't need std::forward if you don't make h accept universal references (Args&&...).
0

Xeo is onto the right idea- you want to build some kind of "variadic iterator" that hides a lot of this nastiness from the rest of the code.

I'd take the index stuff and hide it behind an iterator interface modeled after std::vector's, since a std::tuple is also a linear container for data. Then you can just re-use it all of your variadic functions and classes without having to have explicitly recursive code anywhere else.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.