ysaie

31 / ⚧ / code, music, art, games

──────────────────────────────
🌸 many-shaped creature
✨ too many projects
🚀 cannot be stopped
🌙 stayed up too late
:eggbug: eggbug enjoyer
──────────────────────────────
header image: chapter 8 complete from celeste
avatar: made using this character builder


📩 email
contact@echowritescode.dev

more informative post than my usual "fuck this language" fare, but i found a cool trick today and i wanna share


so, a parameter pack is modern C++'s answer to the janky va_list stuff from C:

template<typename ...ARGUMENTS>
void foo(ARGUMENTS const &...arguments)
{
  // something involving a fold-expression (i'll explain in a sec)
}

ARGUMENTS const &...arguments there more or less means "please generate a unique list of template parameters and function parameters ARGUMENTS_0 const &arguments_0, ARGUMENTS_1 const &arguments_1, ... for whatever list of parameters we were actually passed"

this is pretty neat, but it's a little difficult to get inside the parameter pack. you can't index it, there's no convenient std:: function to do it, and the "unique names" business i mentioned above is just conceptual; there isn't actually an ARGUMENTS_0 or an arguments_0 symbol in scope. so how do?

the way that i've used forever is to just write a recursive function that picks the pack apart one by one:

template<typename HEAD, typename ...REST>
void print_parameter_pack(HEAD const &head, REST const &...rest)
{
  std::cout << head << "\n";
  print_parameter_pack(rest...); // a fold-expression!
}

template<typename LAST>
void print_parameter_pack(LAST const &last)
{
  std::cout << last << "\n";
  // base case, terminates the recursion
}

note the fold-expression here: there are lots of different kinds, but the general idea is "some expression involving the pack name, and then a ..., will expand that expression in-place once per element of the parameter pack". in this case, we are just expanding rest... into the hypothetical parameter list rest_0, rest_1, rest_2, ..., rest_N.

the parameter pack doesn't need to be the only template parameter, after all; it only needs to be the last one.

now, this is great, but what if the algorithm you're writing isn't particularly amenable to recursion? or what if you could potentially recurse thousands and thousands of times and blow out either the compiler's template recursion limit or the actual call stack at runtime?

here's the trick: we steal something from the old javascript texts and use an "immediately invoked function expression"... and then staple the fold operator onto it, and it somehow Just Works (godbolt):

template<typename ...ARGUMENTS>
void print_parameter_pack(ARGUMENTS const &...arguments)
{
    [[maybe_unused]] std::size_t force_evaluation { 0 };
    ([&] {
        ++force_evaluation;
        std::cout << arguments << "\n";
    }(), ...);
}

the force_evaluation variable is there to prevent the whole mess from getting discarded (since there's no return value); other than that, the place where std::cout << arguments << "\n" appears is essentially the "body" of the "loop" (although really, since it's a fold expression, it's literally just an unrolled sequence of these immediately invoked function expressions).

the other odd thing to note is that this actually expands into a comma-separated sequence of expressions, not a semicolon-separated sequence of statements (since you can't fold statements). theoretically it would look like:

template<typename ...ARGUMENTS>
void print_parameter_pack(ARGUMENTS const &...arguments)
{
    [[maybe_unused]] std::size_t force_evaluation { 0 };
    ([&] {
        ++force_evaluation;
        std::cout << arguments_0 << "\n";
    }(),
    [&] {
        ++force_evaluation;
        std::cout << arguments_1 << "\n";
    }(),
    [&] {
        ++force_evaluation;
        std::cout << arguments_2 << "\n";
    }());
}

you can theoretically wrap this up into a utility function to hide the force_evaluation stuff, but i'm not sure if it looks any less gross. if anybody can make a clean abstraction i'd love to see it!

hope somebody finds this useful!! flees the cafe i'm posting this from because it's closing in 10 minutes

credit to this stackoverflow answer for pretty much everything in this post, i more or less just chewed it up and spat it back out for eggbug to carry back to the hive


You must log in to comment.