Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterating through parameters of a variadic function template using variadic lambda

Suppose we have the following function template:

template <typename Functor, typename... Arguments>
void IterateThrough(Functor functor, Arguments&&... arguments)
{
    // apply functor to all arguments
}

This function is usually implemented as follows:

template <typename Functor, typename... Arguments>
void IterateThrough1(Functor functor, Arguments&&... arguments)
{
    int iterate[]{0, (functor(std::forward<Arguments>(arguments)), void(), 0)...};
    static_cast<void>(iterate);
}

Another way:

struct Iterate
{
    template <typename... Arguments>
    Iterate(Arguments&&... arguments)
    {
    }
};

template <typename Functor, typename... Arguments>
void IterateThrough2(Functor functor, Arguments&&... arguments)
{
    Iterate{(functor(std::forward<Arguments>(arguments)), void(), 0)...};
}

I have found yet another approach which uses a variadic lambda:

template <typename Functor, typename... Arguments>
void IterateThrough3(Functor functor, Arguments&&... arguments)
{
    [](...){}((functor(std::forward<Arguments>(arguments)), void(), 0)...);
}

What pros and cons has this method in comparison with first two?

like image 598
Constructor Avatar asked Jun 18 '14 15:06

Constructor


3 Answers

The calls to functor are now unsequenced. The compiler can call functor with your expanded arguments in any order it wants. As an example, IterateThrough3(functor, 1, 2) could do functor(1); functor(2); or it could do functor(2); functor(1);, whereas the other two always do functor(1); functor(2);.

Section 8.5.4/4 of the standard requires that any expressions inside a {} initialiser are evaluated left-to-right.

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear.

Section 5.2.2/4 states that arguments to a function call can be evaluated in any order.

When a function is called, each parameter (8.3.5) shall be initialized (8.5, 12.8, 12.1) with its corresponding argument. [Note: Such initializations are indeterminately sequenced with respect to each other (1.9) — end note ]

This might not cover the wording of the order of evaluation (which I can't find ATM), but it is well known that arguments to functions are evaluated in an unspecified order. EDIT: see @dyp's comment for a relevant standard quote.

like image 72
Simple Avatar answered Oct 16 '22 21:10

Simple


When you use the variadic lambda, the order of evaluation of arguments is unspecified as per the language specification (which in turn means the evaluation of functor(argument) could be in any order unknown to the programmer). That is the only difference. You can search "order of evaluation of arguments" on this site, you will see many topics on it.

As for the variadic templated constructor approach, that should work as long as you use list-initialization to invoke it, otherwise it would have the same problem as the lambda. Note that GCC (upto 4.8.2) has bug, so this doesn't work, though I don't have any idea whether it is fixed with the recent version of GCC.

like image 2
Nawaz Avatar answered Oct 16 '22 20:10

Nawaz


If your goal is to have a one-liner with no external machinery, you can use a lambda that accepts std::initializer_list:

template <typename Functor, typename... Arguments>
void IterateThrough3(Functor functor, Arguments&&... arguments)
{
    [](std::initializer_list<int>){}(
        {((void)functor(std::forward<Arguments>(arguments)), 0)...}
    );
}
like image 1
Casey Avatar answered Oct 16 '22 21:10

Casey