Variadic шаблоны: раскрыть аргументы в группах

16

У меня есть функция, которая принимает два аргумента:

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

И переменный, который должен передавать свои аргументы в парах:

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
}

Я хотел бы bar(1,2,3,4)позвонить foo(1,2)иfoo(3,4)

Есть способ сделать это ?

Fourmet
источник
4
Опасно пересылать одни и те же аргументы дважды
AndyG

Ответы:

13

Вы можете сделать это с перегрузками.

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
}

ЖИТЬ


Обратите внимание, что при использовании приведенного выше минимального фрагмента при вызове barс 0 или нечетными аргументами вы не получите ошибку соответствующей функции . Если вы хотите более четкое сообщение с компиляцией, static_assertвы можете начать с этого фрагмента .

songyuanyao
источник
5

Простая рекурсия с использованием 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)...);
}

Назовите это так:

bar(1, 2, 3, 4);

демонстрация

Я бы сказал, что ответ Songyanyao довольно канонический до C ++ 17. Впоследствии if constexprэто позволило нам перемещать логику в тела наших функций вместо использования трюков с перегрузкой.

AndyG
источник
1
Версия songyanyao довольно легко расширяется, так что она также принимает функцию, которую применяет в качестве аргумента. На мой взгляд, это довольно приятно, поскольку позволяет нам применять этот шаблон несколько раз без необходимости каждый раз писать логику. Есть версия вашего ответа, которая позволяет то же самое?
n314159
1
@ n314159: Что - то вроде этого ?
AndyG
1
Точно! Спасибо. Лично я предпочитаю это, так как (в дополнение к тому, что я уже сказал) он отделяет логику применения от функции, к которой применяется.
n314159
2

Обобщение C ++ 17 для n-ных функторов:

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
источник