Szablony variadic: rozwijaj argumenty w grupach

16

Mam funkcję, która przyjmuje dwa argumenty:

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

I variadic, który powinien przekazywać swoje argumenty parami:

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
}

Chciałbym bar(1,2,3,4)zadzwonić foo(1,2)ifoo(3,4)

Czy jest na to sposób?

Fourmet
źródło
4
Dwukrotne przesyłanie tych samych argumentów jest niebezpieczne
AndyG

Odpowiedzi:

13

Możesz to zrobić z przeciążeniami.

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
}

RELACJA NA ŻYWO


Zauważ, że przy minimalnym fragmencie powyżej podczas wywoływania barz argumentami 0 lub nieparzystymi nie pojawi się błąd pasującej funkcji . Jeśli chcesz uzyskać bardziej przejrzysty komunikat kompilacji static_assert, możesz zacząć od tego fragmentu .

songyuanyao
źródło
5

Prosta rekurencja za pomocą 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)...);
}

Nazwij to tak:

bar(1, 2, 3, 4);

Próbny

Powiedziałbym, że odpowiedź songyanyao jest dość kanoniczna przed C ++ 17. Następnie if constexprpozwoliło nam przenieść logikę do ciał naszych funkcji zamiast używania przeciążających sztuczek.

AndyG
źródło
1
Wersja songyanyao jest dość łatwa do rozszerzenia, dlatego przyjmuje również argument, który stosuje. Moim zdaniem jest to całkiem miłe, ponieważ pozwala nam stosować ten wzór wiele razy bez konieczności zapisywania logiki za każdym razem. Czy istnieje wersja twojej odpowiedzi, która pozwala na to samo?
n314159,
1
@ n314159: Coś jak to ?
AndyG
1
Dokładnie! Dziękuję Ci. Osobiście wolę to, ponieważ (oprócz tego, co już powiedziałem) oddziela logikę zastosowania od funkcji, do której jest stosowana.
n314159,
2

Uogólnienie C ++ 17 dla nfunktorów -ary:

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
źródło