Plantillas variables: desplegar argumentos en grupos

16

Tengo una función que toma dos argumentos:

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

Y una variable que debe reenviar sus argumentos en pares:

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
}

Querría bar(1,2,3,4) llamar foo(1,2)yfoo(3,4)

Hay una manera de hacerlo ?

Fourmet
fuente
44
Es peligroso reenviar los mismos
argumentos

Respuestas:

13

Puedes lograrlo con sobrecargas.

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
}

EN VIVO


Tenga en cuenta que con el fragmento mínimo anterior al llamar barcon 0 o argumentos impares no obtendrá un error de función coincidente . Si desea un mensaje de compilación más claro static_assert, puede comenzar desde este fragmento .

songyuanyao
fuente
5

Recurrencia simple usando 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)...);
}

Llámalo así:

bar(1, 2, 3, 4);

Manifestación

Yo diría que la respuesta de songyanyao es bastante canónica antes de C ++ 17. Posteriormente, if constexprnos ha permitido mover la lógica a los cuerpos de nuestras funciones en lugar de usar trucos de sobrecarga.

AndyG
fuente
1
La versión de songyanyao es bastante fácil de extender, de modo que también toma la función que está aplicando como argumento. En mi opinión, esto es bastante bueno, ya que nos permite aplicar este patrón varias veces sin tener que escribir la lógica cada vez. ¿Hay una versión de su respuesta que permita lo mismo?
n314159
1
@ n314159: ¿Algo como esto ?
AndyG
1
¡Exactamente! Gracias. Personalmente, prefiero esto, ya que (además de lo que ya dije) separa la lógica de aplicación de la función a la que se aplica.
n314159
2

Generalización de C ++ 17 para nfunarios de canario:

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
fuente