What's va_arg() in C++11 variadic template? [d

2019-03-20 17:21发布

问题:

This question already has an answer here:

  • C++ index of type during variadic template expansion 2 answers

I've read some articles about this new C++11 feature but I didn't understand all stuff (I'm new to C++). How do I access a specific argument like I can do using va_arg from stdarg.h in C?

template <typename ... Args>
void f(Args ... args)
{
    for(size_t i = 0; i < sizeof ...(args); i++)
    {
        // obviously, args...[i] didn't work...
    }
}

回答1:

The problem is with TYPE var = args[c];, what do you write for TYPE? Each i has a different type, so you won't be able to use a for loop like this.

Generally, the normal approach is to use recursion.

void f() { } //end loop

template<class FirstType, typename...Args> 
void f(FirstType&& first, Args&&...rest) {  
    //do loop body
    loop_body(std::forward<FirstType>(first)...)
    //do next "iteration"
    f(std::forward<Args>(rest)...);
}

There's also this way to do it without recursion, but it's a bit more advanced:

template<typename...Args> 
void f(Args&&...args) {  
    typedef int[] for_each;
    for_each{((void)(   loop_body(std::forward<Args>(args))  ),0)...,0};
}

And finally, if you really want to access one by index:

//note that "i" must be a compile time constant
auto var = std::get<i>(std::tie(std::forward<Args>(args)...));


The typedef int[] code is very strange, and so I'll lay it out here.
We'd like to call a function loop_body(std::forward<Args>(args))...; but unfortunately, parameter packs can only be expanded in certain contexts, and that's not one of them. The easiest and most obvious solution is to pass the results of all those calls to a function that does nothing: do_nothing(loop_body(std::forward<Args>(args))...), but unfortunately, this fails for void return types, because you can't instantiate a void to pass to the do_nothing. Worse, it could call each of the functions in the wrong order. One way to "convert" a void expression to something else is with an arcane trick with the comma operator, (func(), 0) executaes func and then "returns" 0.
Worse, for reasons that I admittedly don't understand, f(vs)...,0; nor (f(vs),0)...,0; are valid contexts for expanding parameter packs. However, type array[] = {vs...} is a valid context. So now we have a way to combine this context with expressions with return values: int array[] = {(f(vs),0)...}; And it works! Mostly!
If the parameter pack has zero types (yes, that's valid. never forget it.), then this results in a compiler error. So we have to add one additional zero on the end so there is always at least one element: int array[] = {(f(vs),0)..., 0};. Also, most compilers warn that array is an unused variable. One way to bypass that warning is to make the type a temporary. int a = (expr); is a local, but (int)(expr) creates an unnamed temporary. So we want (int []){(f(vs),0)..., 0};. For reasons I can't recall, this (int[]) normally hidden behind a typedef. As a final detail, since some classes can overload the comma operator, it's safest to cast the functions to void: int array[] = {((void)(f(vs)),0)..., 0};

Unrelated, I've contemplated this macro in the past, which hides the ugly details a little more. But I feel like there's a downside I'm overlooking or else it would be more common.

#define FOREACH_VARIADIC(EXPR) (int[]){((void)(EXPR),0)...,0}    

template<typename...Args> 
void f(Args&&...args) {  
    FOREACH_VARIADIC(loop_body(std::forward<Args>(args)));
}


回答2:

Accepted answer is very good, but here's an idea using C++14 generic lambdas:

template <typename F>
void variadic_for_each(F) {}

template <typename F, typename Head, typename... Tail>
void variadic_for_each(Head&& head, Tail&&... tail, F f)
{
    f(std::forward<Head>(head));
    variadic_for_each(std::forward<Tail>(tail)..., f);
}

Example usage:

template <typename... Ts>
void myFunc(Ts&&... vs)
{
    variadic_for_each(std::forward<Ts>(vs)..., [](auto&& v)
    {
        // loop body
    });
}