Ответ 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!");
}
В заключение, как обычно, живой пример.