Home > Mobile >  Iterating over Variadic arguments
Iterating over Variadic arguments

Time:11-08

I'm sure this question has been asked before, but I can't seem to find something as simple as what I'm trying to do. Essentially, I just want to make sure the code triggers each parameter, calling any functions that may be sent as inputs. My biggest worry here is that optimizations may remove some of the calls, changing the result.

I was using the following syntax. It seemed to trigger the function calls the way I want, but it has the strange result of triggering the arguments in reverse order - the last argument is called first, and the first argument is called last:

template <typename... PARMS> uint PARMS_COUNT(PARMS&& ... parms) { return static_cast<uint>( sizeof...(parms) ); }

This was my first guess as to how to do the same thing, but in the correct order:

template <typename FIRST>
constexpr uint PARMS_EXPAND(FIRST &&first)
{
    return static_cast<uint>( sizeof(first) > 0 ? 1 : 0 );
}
template <typename FIRST,typename... PARMS>
constexpr uint PARMS_EXPAND(FIRST &&first,PARMS&& ... parms)
{
    return static_cast<uint>( sizeof(first) > 0 ? 1 : 0 )   PARMS_EXPAND( std::forward<PARMS>(parms)... );
}

I tested this in a few places, but then realized that regardless of how much testing I do, I'll never know if this is a safe way to do it. Is there a standard or well known method to pull this logic off? Or even better, some built in system to iterate over the arguments and "access them" in the correct order?

To better explain why I would want to trigger code like this, consider a function that can add new objects to a parent:

void AddObject(/*SINGLE UNKNOWN INPUT*/)
{
    ...
}
template <typename... PARMS> AddObjects(PARMS&& ... parms)
{
    PARAMS_EXPAND( AddObject(parms)... );
}

CodePudding user response:

When you write

void AddObject(T);

template <typename... PARMS> AddObjects(PARMS&& ... parms)
{
    PARAMS_EXPAND( AddObject(parms)... );
}

you're making a single top-level function call to PARAMS_EXPAND with sizeof...(PARMS) arguments.

What happens inside PARAMS_EXPAND is essentially irrelevant because, like every other function call, the arguments are evaluated before the function is entered. And, like every other function call, the argument expressions are intederminately sequenced with respect to each other.

So, your goal of evaluating AddObject(parms) for each parameter in order has failed before control reaches inside PARAMS_EXPAND at all.

Fortunately, C 17 gave us a way of evaluating operations on parameter packs in order without relying on function call semantics: the fold expression:

(AddObject(parms), ...);

Although this looks a bit like a function call, it's actually a right fold over the comma operator, expanded like

(AddObject(p0) , (... , (AddObject(pN-1) , AddObject(PN))));

which does indeed evaluate its arguments in left-to-right order, exactly as you want.

CodePudding user response:

I answer the original question because it's not clear what you're asking for (see comment) later. If you want n arguments to be called in order, i.e. call operator() on them (e.g. for lambdas, functors), you can do:

constexpr void callEach() {}

template<typename F, typename... Fs>
constexpr void callEach(F f, Fs&&... fs)
{
    f();
    callEach(std::forward<Fs>(fs)...);
}

Any way that doesn't take lambdas cannot guarantee evaluation order.

CodePudding user response:

In this line:

PARAMS_EXPAND( AddObject(parms)... );

the order of evaluation is not specified because there is no sequence point between each item. Also I'm confused as to why PARAMS_EXPAND does anything if you are going to discard the result. It might as well be this:

template <typename... PARMS>
void PARAMS_EXPAND(PARMS&&...) {}

If you just want to call AddObject with each parms in order, then you need to pass the function and the parameters separately:

void print(int i) {
    std::cout << i << " ";
}

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

template <typename... Args>
void print_all(Args&&... args)
{
    call_for_each(print, std::forward<Args>(args)...);
}

Demo

Here the calls are correctly sequenced: first call f with head, then make the recursive call for the rest of the arguments in tail.

In C 17, you can use a fold expression instead:

template <typename F, typename... Args>
constexpr void call_for_each(F&& f, Args&&... args)
{
    (f(std::forward<Args>(args)), ...);
}

Demo

This basically generates a line like so:

(f(std::forward<Args0>(args0), f(std::forward<Args1>(args1), ..., f(std::forward<ArgsN>(argsN));

where ArgsI and argsI are the I-th element of Args and args in the correct order.

Here, the calls are again correctly sequenced because of the comma operator.

  • Related