0

https://github.com/google/oboe/blob/e3e93e307456a388a84a6e0d96f9adb240f5918d/apps/fxlab/app/src/main/cpp/native-lib.cpp#L93

std::visit([id](auto &&stack) {
    std::function<void(decltype(stack.getType()), decltype(stack.getType()))> f;
    int i = 0;
    std::apply([id, &f, &i](auto &&... args) mutable {
        ((f = (i++ == id) ?
              args.template buildDefaultEffect<decltype(stack.getType())>() : f), ...);
    }, EffectsTuple);
    stack.addEffect(std::move(f));
}, enginePtr->functionList);

std::visit applies the lambda to the vector functionList. The lambda, applies the lambda inside of it to the EffectsTuple.

However, I'm having a hard time understanding

((f = (i++ == id) ?
              args.template buildDefaultEffect<decltype(stack.getType())>() : f), ...);

What does the outer most () does? what is the second argument ...? What does args.template mean?

Wyck
  • 10,311
  • 6
  • 39
  • 60
Guerlando OCs
  • 1,886
  • 9
  • 61
  • 150
  • `()` does what it does most of the time: make a function call. `...` designates a fold expression. `args.template` invokes a template member of `args`. Perhaps if you asked one question per stackoverflow.com question it might be meaningful to answer with a capsule summary of the relevant C++17 features, but for a bunch of questions about basic C++17 functionality, the best common answer would be to refer you to a recent textbook covering advance template usage, and C++17 features. – Sam Varshavchik Jan 02 '21 at 22:36
  • @SamVarshavchik The outer `()` aren't a function call. They are part of the fold expression syntax. – HTNW Jan 02 '21 at 23:35

3 Answers3

2

what is the second argument ...?

(foo(args), ...) is a fold expression, using comma operator, equivalent to (foo(arg0), foo(arg1), .., foo(argn))

What does args.template mean?

template is used to disambiguate meaning of < for dependent type:

without that, it would be parsed as

(args.buildDefaultEffect < decltype(stack.getType())) > ().

see where-and-why-do-i-have-to-put-the-template-and-typename-keywords.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

That ... is a right fold over the comma operator. What that code essentially does is call the buildDefaultEffect function for the args variable with the requested id, assigns the return value to f and then adds f to the stack with stack.addEffect.

Basically that code expands to something like

(f = f, f = f, /*i == id*/ f = args.buildDefaultEffect(stack.getType()), f = f, f = f);
stack.addEffect(f);
cigien
  • 57,834
  • 11
  • 73
  • 112
Yamahari
  • 1,926
  • 9
  • 25
2
((f = (i++ == id) ?
          args.template buildDefaultEffect<defaultEffect<decltype(stack.getType())>() : f), ...);

this is a comma-fold execute pack expansion. It is an awkard way to iterate over non-uniform typed elements in a tuple.

If you could iterate over a pack of non-uniform elements:

int i=0;
for...(auto& arg:args...){
  if (i!=id){
    ++i;
    continue;
  }
  f=arg.template buildDefaultEffect<defaultEffect<decltype(stack.getType())>();
  break;
}

this is the code they'd like to write. But they cannot.

So they use comma-fold execute to generate something equivalent to the body of the loop above for each arg in args.

And in the "do nothing" bodies they do f=f.

Myself I'd do:

((i++ == id) ?
          f=args.template buildDefaultEffect<defaultEffect<decltype(stack.getType())>() : nullptr), ...);

which is both clearer and more efficient.

Alternatively, foreach_arg is a useful alternative to write. Or making a n-sized array of function pointers, each one doing a loop body, and only running one.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • `cond ? f = got_stdFunction() : nullptr` might still create an empty `std::function`, immediate lambda `[&](){ if (i++ == id) { f = args.template buildDefaultEffect();} }(), ...)` seems even better. – Jarod42 Jan 03 '21 at 00:42
  • @jarod42 empty std functions are pretty trivial. I'd bet they optimize to zilch. – Yakk - Adam Nevraumont Jan 03 '21 at 00:46
  • With type-erasure type, I won't bet ;) (self assignment should also be easy to optimize btw IMO). For readability point, the `nullptr` trick only works for `std::function` (and other "nullable" types), lambda can be transposed to other types :) – Jarod42 Jan 03 '21 at 00:54
  • and in above case, I might even create `std::function` array, and just do `f = funcs[id];`. – Jarod42 Jan 03 '21 at 01:03
  • @Jarod42 Naw, `function( std::nullptr_t ) noexcept;` -- it does no allocation when constructed from `nullptr`. The null `std::function` is different than the non-null one. – Yakk - Adam Nevraumont Jan 03 '21 at 01:14
  • why the `break`? Could somebody explain how the fold expression introduces a `break`? – Guerlando OCs Jan 13 '21 at 06:18
  • @guer it does not; it just repeatedly does `f=f`. This is a "do nothing" operation. Before I treated it as "continue", after "break". I said "if they could iterate, here is what they would write" not "the fold expression does these things". – Yakk - Adam Nevraumont Jan 13 '21 at 10:02