3
\$\begingroup\$

Interactive Wandbox example

Inspired by this Stack Oveflow question, I've implemented a function that, when called with a desired arity, a callable object, and a variadic amount of arguments, calls the passed callable object forwarding the passed arguments in groups of the desired arity.

Example:

auto printAll([](auto... xy){ (std::cout << ... << xy); std::cout << " "; });

forNArgs<2> // `2` is the desired arity
(
    // `printAll` is the callable object.
    printAll,        

    // Variadic arguments.
    0, 1,   2, 3,   4, 5,   6, 7
);

// ...will result in code equivalent to...

printAll(0, 1);
printAll(2, 3);
printAll(4, 5);
printAll(6, 7);

I'm trying to simplify the original code using fold expressions. This is what I have so far:

// "Third step": call `mFn` once getting the correct elements from the forwarded tuple.
template<std::size_t TIStart, typename TF, typename TTpl, std::size_t... TIs>
void forNArgsStep(TF&& mFn, TTpl&& mTpl, std::index_sequence<TIs...>)
{
    mFn(std::get<TIStart + TIs>(mTpl)...);
}

// "Second step": use a comma operator fold expression to call the "third step" `numberOfArgs / TArity` times.
template<std::size_t TArity, typename TF, typename TTpl, std::size_t... TIs>
void forNArgsExpansion(TF&& mFn, TTpl&& mTpl, std::index_sequence<TIs...>)
{
    using SeqGet = std::make_index_sequence<TArity>;
    (forNArgsStep<TIs * TArity>(mFn, mTpl, SeqGet{}), ...);
}

// "First step / interface".
template<std::size_t TArity, typename TF, typename... Ts>
void forNArgs(TF&& mFn, Ts&&... mXs)
{
    constexpr auto numberOfArgs(sizeof...(Ts));

    static_assert(numberOfArgs % TArity == 0,
        "Invalid number of arguments");

    auto&& asTpl(std::forward_as_tuple(std::forward<Ts>(mXs)...));

    // We need to "convert" the `Ts...` pack into a "list" of packs
    // of size `TArity`.

    using SeqCalls = std::make_index_sequence<numberOfArgs / TArity>;
    forNArgsExpansion<TArity>(mFn, asTpl, SeqCalls{});
}

More examples:

int main()
{
    // Prints "01 23 45 67":
    forNArgs<2>
    (
        [](auto... xy){ (std::cout << ... << xy); std::cout << " "; },

        0, 1, /**/ 2, 3, /**/ 4, 5, /**/ 6, 7
    );
    std::cout << "\n";

    // Prints "abc def ghi":
    forNArgs<3>
    (
        [](auto... xyz){ (std::cout << ... << xyz); std::cout << " "; },

        "a", "b", "c", /**/ "d", "e", "f", /**/ "g", "h", "i"
    );
    std::cout << "\n";
}

I think my perfect forwarding is incorrect.

  • When should I forward the tuple?
  • When should I forward the function?

How can I simplify the code further?

  • Is there any way of avoiding the two extra helper functions forNArgsExpansion and forNArgsStep?
  • Is there any way of avoiding the creation of a tuple? Can the arguments be forwarded in a better way?
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.