Как изменить порядок аргументов функции вариационного шаблона?

У меня есть функция шаблона с аргументами шаблона varargs, например

template<typename Args...>
void ascendingPrint(Args... args) { /* ... */ }

И я хочу написать

template<typename Args...>
void descendingPrint(Args... args) {
  /* implementation using ascendingPrint()? */
}

Как изменить порядок пакета параметров args, прежде чем передавать его, т.е. в псевдокоде:

template<typename Args...>
void descendingPrint(Args... args) {
  ascendingPrint( reverse(args) );
}

Ответ 1

Вот рекурсивная реализация специализированного revert<>:

// forward decl
template<class ...Tn>
struct revert;

// recursion anchor
template<>
struct revert<>
{
    template<class ...Un>
    static void apply(Un const&... un)
    {
        ascendingPrint(un...);
    }
};

// recursion
template<class T, class ...Tn>
struct revert<T, Tn...> 
{
    template<class ...Un>
    static void apply(T const& t, Tn const&... tn, Un const&... un)
    {
        // bubble 1st parameter backwards
        revert<Tn...>::apply(tn..., t, un...);
    }
};

// using recursive function
template<class A, class ...An>
void descendingPrint(A const& a, An const&... an)
{
    revert<An...>::apply(an..., a);
}

Он работает с gcc-4.6/7/8 и clang и, вероятно, стандартно совместим - единственной трудной частью является вызов revert<Tn...>::apply(tn..., t, un...).

У этого есть недостатки, хотя (как часто рекурсия), что он генерирует много шаблонов-экземпляров целевой функции (раздувание кода) и не использует идеальную пересылку, что может быть проблемой (но, возможно, может быть улучшено до используйте его).

Ответ 2

Общий подход и использование


Изолированный подход состоит в том, чтобы скомпоновать аргументы в std::tuple ссылок, используя совершенную переадресацию машины std::forward_as_tuple().

Это означает, что во время выполнения вы должны выполнять очень небольшие накладные расходы и не выполнять лишние операции копирования/перемещения. Кроме того, инфраструктура не использует рекурсию (помимо репликации времени компиляции, которая неизбежна для генерации индексов), поэтому риск потери времени выполнения не может быть даже в том случае, если компилятору не удалось встроить вызовы рекурсивных функций (что маловероятно во всяком случае, так что это скорее академический аргумент).

Кроме того, это решение является общим, поскольку вы можете использовать его как библиотеку только для заголовка, чтобы вызывать ваши функции с помощью обратных аргументов и с минимальными усилиями: descending_print() должен быть просто минимальной тонкой оболочкой вокруг ascending_print().

Вот как это должно выглядеть:

MAKE_REVERT_CALLABLE(ascending_print)

template<typename... Args>
void descending_print(Args&&... args)
{
    revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...);
} 

Далее следует презентация реализации.


Первый шаг: возврат последовательности типов


Вот простой способ вернуть последовательность типов:

#include <tuple>
#include <type_traits>

template<typename, typename>
struct append_to_type_seq { };

template<typename T, typename... Ts>
struct append_to_type_seq<T, std::tuple<Ts...>>
{
    using type = std::tuple<Ts..., T>;
};

template<typename... Ts>
struct revert_type_seq
{
    using type = std::tuple<>;
};

template<typename T, typename... Ts>
struct revert_type_seq<T, Ts...>
{
    using type = typename append_to_type_seq<
        T,
        typename revert_type_seq<Ts...>::type
        >::type;
};

Небольшая тестовая программа:

int main()
{
    static_assert(
        std::is_same<
            revert_type_seq<char, int, bool>::type,
            std::tuple<bool, int, char>
            >::value,
        "Error"
        );
}

И живой пример.


Второй шаг: возврат кортежа


Следующий шаг состоит в возврате кортежа. Учитывая обычные показатели трюков:

template <int... Is>
struct index_list { };

namespace detail
{
    template <int MIN, int N, int... Is>
    struct range_builder;

    template <int MIN, int... Is>
    struct range_builder<MIN, MIN, Is...>
    {
        typedef index_list<Is...> type;
    };

    template <int MIN, int N, int... Is>
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
    { };
}

template<int MIN, int MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;

Вместе с функциями, определенными выше, кортеж можно легко вернуть таким образом:

template<typename... Args, int... Is>
typename revert_type_seq<Args...>::type
revert_tuple(std::tuple<Args...> t, index_list<Is...>)
{
    using reverted_tuple = typename revert_type_seq<Args...>::type;

    // Forwarding machinery that handles both lvalues and rvalues...
    auto rt = std::forward_as_tuple(
            std::forward<
                typename std::conditional<
                    std::is_lvalue_reference<
                        typename std::tuple_element<Is, reverted_tuple>::type
                        >::value,
                    typename std::tuple_element<Is, reverted_tuple>::type,
                    typename std::remove_reference<
                        typename std::tuple_element<Is, reverted_tuple>::type
                        >::type
                    >::type
                >(std::get<sizeof...(Args) - Is - 1>(t))...
        );

    return rt;
}

template<typename... Args>
typename revert_type_seq<Args...>::type
revert_tuple(std::tuple<Args...> t)
{
    return revert_tuple(t, index_range<0, sizeof...(Args)>());
}

Вот простая тестовая программа:

#include <iostream>

int main()
{
    std::tuple<int, int, char> t(42, 1729, 'c');
    auto rt = revert_tuple(t);

    std::cout << std::get<0>(rt) << " "; // Prints c
    std::cout << std::get<1>(rt) << " "; // Prints 1729
    std::cout << std::get<2>(rt) << " "; // Prints 42
}

Вот живой пример.


Третий шаг: возврат аргументов функции


Последний шаг заключается в распаковке кортежа при вызове нашей целевой функции. Вот еще одна общая утилита, которая поможет нам сэкономить пару строк:

template<typename... Args>
typename revert_type_seq<Args...>::type
make_revert(Args&&... args)
{
    auto t = std::forward_as_tuple(std::forward<Args>(args)...);
    return revert_tuple(t);
}

Вышеупомянутая функция создает кортеж, элементами которого являются предоставленные аргументы, но в обратном порядке. Мы не готовы определить нашу цель:

template<typename T>
void ascending_print(T&& t)
{
    std::cout << std::forward<T>(t) << " ";
}

template<typename T, typename... Args>
void ascending_print(T&& t, Args&&... args)
{
    ascending_print(std::forward<T>(t));
    ascending_print(std::forward<Args>(args)...);
}

Вышеуказанная функция печатает все предоставленные аргументы. И вот как мы могли написать descending_print():

template<typename T, int... Is>
void call_ascending_print(T&& t, index_list<Is...>)
{
    ascending_print(std::get<Is>(std::forward<T>(t))...);
}

template<typename... Args>
void descending_print(Args&&... args) {
    call_ascending_print(make_revert(std::forward<Args>(args)...),
         index_range<0, sizeof...(Args)>());
}

Простой тестовый пример:

int main()
{
    ascending_print(42, 3.14, "Hello, World!");
    std::cout << std::endl;
    descending_print(42, 3.14, "Hello, World!");
}

И, конечно, живой пример.


Конечный шаг: упрощение


Вышеупомянутое решение может быть нетривиальным для понимания, но его можно сделать тривиальным в использовании и достаточно гибким. Учитывая пару общих функций:

template<typename F, typename... Args, int... Is>
void revert_call(F&& f, index_list<Is...>, Args&&... args)
{
    auto rt = make_revert(std::forward<Args>(args)...);
    f(std::get<Is>(rt)...);
}

template<typename F, typename... Args>
void revert_call(F&& f, Args&&... args)
{
    revert_call(f, index_range<0, sizeof...(Args)>(), 
                std::forward<Args>(args)...);
}

И несколько макроопределений (я не смог найти способ создания набора перегрузки для шаблона функции, извините):

#define MAKE_REVERT_CALLABLE(func) \
    struct revert_caller_ ## func \
    { \
        template<typename... Args> void operator () (Args&&... args) \
        { func(std::forward<Args>(args)...); } \
    };

#define REVERT_ADAPTER(func) \
    revert_caller_ ## func()

Очень легко адаптировать любую функцию для вызова с аргументами в обратном порядке:

MAKE_REVERT_CALLABLE(ascending_print)

template<typename... Args>
void descending_print(Args&&... args)
{
    revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...);
}

int main()
{
    ascending_print(42, 3.14, "Hello, World!");
    std::cout << std::endl;
    descending_print(42, 3.14, "Hello, World!");
}

В заключение, как обычно, живой пример.

Ответ 3

Я думаю вместо того, чтобы отменить аргументы, вы можете отменить свою логику! Например, отмените операции над аргументами.

template <typename T>
void ascendingPrint(const T& x)
{
    cout << x << " ";
}

template<typename T, typename ... Args>
void ascendingPrint(const T& t, Args... args)
{
    ascendingPrint(t);                   // First print `t`
    ascendingPrint(args...);             // Then print others `args...`
}

template <typename T>
void descendingPrint(const T& x)
{
    cout << x << " ";
}

template<typename T, typename ... Args>
void descendingPrint(const T& t, Args... args)
{
    descendingPrint(args...);            // First print others `args...`
    descendingPrint(t);                  // Then print `t`
}

int main()
{
    ascendingPrint(1, 2, 3, 4);
    cout << endl;
    descendingPrint(1, 2, 3, 4);
}

Выход

1 2 3 4 
4 3 2 1 

Ответ 4

Вот простой подход, который я упомянул в комментариях: Создание индексов в обратном порядке и распаковка кортежа с этим.

// reversed indices...
template<unsigned... Is> struct seq{ using type = seq; };

template<unsigned I, unsigned... Is>
struct rgen_seq : rgen_seq<I-1, Is..., I-1>{};

template<unsigned... Is>
struct rgen_seq<0, Is...> : seq<Is...>{};

#include <tuple>

namespace aux{
template<class Tup, unsigned... Is>
void descending_print(Tup&& t, seq<Is...>)
{
    ascending_print(std::get<Is>(std::forward<Tup>(t))...);
}
} // aux::

template<class... Args>
void descending_print(Args&&... args)
{
    auto t = std::forward_as_tuple(std::forward<Args>(args)...);
    aux::descending_print(t, rgen_seq<sizeof...(Args)>{});
}

Пример в реальном времени.

Ответ 5

Мое решение поддерживает идеальную пересылку и не требует рекурсии:

#include <iostream>
#include <utility>
#include <tuple>

#include <cstdlib>

template< typename ...types >
void
ascendingPrint(types &&... _values)
{
    (std::cout << ... << std::forward< types >(_values)) << std::endl;
}

template< typename ...types, std::size_t ...indices >
void
descendingPrintHelper(std::tuple< types... > const & refs, std::index_sequence< indices... >)
{
    constexpr std::size_t back_index = sizeof...(indices) - 1;
    return ascendingPrint(std::forward< std::tuple_element_t< back_index - indices, std::tuple< types... > > >(std::get< back_index - indices >(refs))...);
}

template< typename ...types >
void
descendingPrint(types &&... _values)
{
    auto const refs = std::forward_as_tuple(std::forward< types >(_values)...);
    return descendingPrintHelper(refs, std::make_index_sequence< sizeof...(types) >{});
}

int
main()
{
    ascendingPrint(1, ' ', 2, ' ', 3);
    descendingPrint(1, ' ', 2, ' ', 3);
    return EXIT_SUCCESS;
}

Пример в реальном времени (или даже проще).

Также современные компиляторы могут отлично оптимизировать все ненужные вещи: https://godbolt.org/g/01Qf6w