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)

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319

5 Answers5

31

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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • `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. – Andrew Tomazos Aug 19 '12 at 23:12
  • 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. :) – Xeo Aug 19 '12 at 23:18
  • Actually if you look at code sample here: http://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. – Andrew Tomazos Aug 19 '12 at 23:22
  • In your first sample I think you can discard `length_of` and just call `sizeof...` again, no? – Andrew Tomazos Aug 19 '12 at 23:36
  • @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 – Xeo Aug 19 '12 at 23:38
  • Also is it possible to create a temporary array? If so first sample function body could be `{ g(X[] { f(args)...}, sizeof...(args)); }`. Not sure if this works. – Andrew Tomazos Aug 19 '12 at 23:41
  • You don't need recursion if you expand the calls to `f` as a parameter pack expansion; see [my answer](http://stackoverflow.com/a/12030882/567292). – ecatmur Aug 19 '12 at 23:49
  • @Andrew: I thought of that at first too, but you're not allowed to take the address of a temporary, and here the array'd need to decay. A little helper function works though, to make it an lvalue. Edited that in. Beware of the `as_lvalue` function, though. – Xeo Aug 19 '12 at 23:50
  • @Xeo: I think your first sample is the winner for being cleanest. – Andrew Tomazos Aug 19 '12 at 23:51
  • @ecatmur: Good point, but not as you show it in your answer. :) Can be done with the indices-trick that makes a pack of indices from a parameter pack and then does `f(xs[indices], args)...)`. Too lazy to write that up, though. – Xeo Aug 19 '12 at 23:53
  • @Andrew: The other three of the same idea avoid the copies, though. And you can even make that wrapper `g` a lambda inside of `h`: `auto gw = [](std::initializer_list const& xs){ g(xs.begin(), xs.size()); }; gw({f(args)...});`, and I must say I personally like that one better. – Xeo Aug 19 '12 at 23:54
  • @Xeo nah, brace-initializer-lists are sequenced. I've added the index parameter pack technique though. – ecatmur Aug 20 '12 at 00:17
  • @ecatmur: Meh, I did just that too. xD And thanks, didn't know the part about `braced-init-list`s being sequenced left-to-right. – Xeo Aug 20 '12 at 00:18
8

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)...);
}
Victor Laskin
  • 2,577
  • 2
  • 16
  • 10
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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 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 Tomazos Aug 19 '12 at 22:56
  • @Andrew: You shouldn't rely on compiler extensions. – Xeo Aug 19 '12 at 22:57
  • 2
    @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 Tomazos Aug 19 '12 at 22:57
  • 1
    @Andrew: Even then you shouldn't, requirements can change, as can support for extensions. – Xeo Aug 19 '12 at 22:58
  • @AndrewTomazos-Fathomling: I'd agree with you somewhat if the extension really adds something that would otherwise be difficult or impossible to express. But in cases where a simple, standard alternative exists (like this one), the extension becomes useless and is unnecessarily unreliable. – Ken Wayne VanderLinde Aug 19 '12 at 23:01
  • @KenWayneVanderLinde: Actually yes, I agree with you. A better solution would be to just declare nargs constexpr. – Andrew Tomazos Aug 19 '12 at 23:02
  • 3
    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. – Dietmar Kühl Aug 19 '12 at 23:06
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.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 1
    Is the parameter pack guaranteed to be evaluated in order? I seem to remember that it isn't, but I could be misremembering. – Andrew Tomazos Aug 19 '12 at 23:47
  • @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). – ecatmur Aug 20 '12 at 00:12
  • 1
    You don't need `std::forward` if you don't make `h` accept universal references (`Args&&...`). – Xeo Aug 20 '12 at 00:19
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.

Zack Yezek
  • 1,408
  • 20
  • 7