Variadische Vorlagen: Entfalten Sie Argumente in Gruppen

16

Ich habe eine Funktion, die zwei Argumente akzeptiert:

template <typename T1, typename T2>
void foo(T1 arg1, T2 arg2)
{ std::cout << arg1 << " + " << arg2 << '\n'; }

Und eine Variante, die ihre Argumente paarweise weiterleiten sollte:

template <typename... Args>
void bar(Args&&... args) {
    static_assert(sizeof...(Args) % 2 == 0);

    ( foo( std::forward<Args>(args), std::forward<Args>(args) ), ... );
    // ^ Sends each argument twice, not in pairs
}

Ich würde gerne bar(1,2,3,4)anrufen foo(1,2)undfoo(3,4)

Gibt es eine Möglichkeit, das zu tun?

Fourmet
quelle
4
Es ist gefährlich, die gleichen Argumente zweimal
weiterzuleiten

Antworten:

13

Sie können dies mit Überlastungen erreichen.

template <typename T1, typename T2>
void bar(T1&& arg1, T2&& arg2) {
    foo( std::forward<T1>(arg1), std::forward<T2>(arg2) ); // (until) sends (the last) two arguments to foo
}

template <typename T1, typename T2, typename... Args>
void bar(T1&& arg1, T2&& arg2, Args&&... args) {
    foo( std::forward<T1>(arg1), std::forward<T2>(arg2) ); // sends the 1st two arguments to foo
    bar( std::forward<Args>(args)... );                    // call bar with remaining elements recursively
}

LEBEN


Beachten Sie, dass mit dem oben angegebenen minimalen Snippet beim Aufrufen barmit 0 oder ungeraden Argumenten kein übereinstimmender Funktionsfehler angezeigt wird. Wenn Sie eine klarere Kompilierungsnachricht wünschen, static_assertkönnen Sie mit diesem Snippet beginnen .

songyuanyao
quelle
5

Einfache Rekursion mit if constexpr:

// print as many pairs as we can
template<class T, class U, class... Args>
void foo(T t, U u, Args&&... args)
{
    std::cout << t << " + " << u << "\n";
    if constexpr(sizeof...(Args) > 0 && sizeof...(Args) % 2 == 0)
        foo(std::forward<Args>(args)...);
}

template<class... Args>
void bar(Args&&... args)
{
    static_assert(sizeof...(Args) % 2 == 0);
    foo(std::forward<Args>(args)...);
}

Nennen Sie es so:

bar(1, 2, 3, 4);

Demo

Ich würde sagen, dass die Antwort von songyanyao vor C ++ 17 ziemlich kanonisch ist. Danach if constexprhaben wir die Logik in die Körper unserer Funktionen verschoben, anstatt Überladungstricks anzuwenden.

AndyG
quelle
1
Die Version von songyanyao ist ziemlich einfach zu erweitern, so dass sie auch die Funktion, die sie anwendet, als Argument verwendet. Meiner Meinung nach ist dies ziemlich nett, da wir dieses Muster mehrmals anwenden können, ohne jedes Mal die Logik schreiben zu müssen. Gibt es eine Version Ihrer Antwort, die dasselbe zulässt?
n314159
1
@ n314159: So etwas wie diese ?
AndyG
1
Genau! Vielen Dank. Persönlich bevorzuge ich dies, da es (zusätzlich zu dem, was ich bereits gesagt habe) die Apply-Logik von der Funktion trennt, auf die es angewendet wird.
n314159
2

C ++ 17-Verallgemeinerung für n-ary-Funktoren:

namespace impl
{
    template<std::size_t k, class Fn, class Tuple, std::size_t... js>
    void unfold_nk(Fn fn, Tuple&& tuple, std::index_sequence<js...>) {
        fn(std::get<k + js>(std::forward<Tuple>(tuple))...);
    }

    template<std::size_t n, class Fn, class Tuple, std::size_t... is>
    void unfold_n(Fn fn, Tuple&& tuple, std::index_sequence<is...>) {
        (unfold_nk<n * is>(fn, std::forward<Tuple>(tuple), 
            std::make_index_sequence<n>{}), ...);
    }
}

template<std::size_t n, class Fn, typename... Args>
void unfold(Fn fn, Args&&... args) {
    static_assert(sizeof...(Args) % n == 0);
    impl::unfold_n<n>(fn, std::forward_as_tuple(std::forward<Args>(args)...), 
        std::make_index_sequence<sizeof...(Args) / n>{});
}

int main() {
    auto fn = [](auto... args) { 
        (std::cout << ... << args) << ' ';
    };

    unfold<2>(fn, 1, 2, 3, 4, 5, 6);   // Output: 12 34 56
    unfold<3>(fn, 1, 2, 3, 4, 5, 6);   // Output: 123 456
}
Evg
quelle