approccio generale e l'uso di
L'approccio overal consiste nel confezionamento degli argomenti in un std::tuple
di riferimenti, sfruttando la perfetta inoltro macchinari di std::forward_as_tuple()
.
Ciò significa che, in fase di esecuzione, è necessario sostenere un sovraccarico molto piccolo e nessuna operazione di copia/spostamento non necessaria. Inoltre, il framework non utilizza la ricorsione (a parte la ricorsione in fase di compilazione, che è inevitabile per la generazione di indici), quindi nessun rischio di sovraccarico in fase di esecuzione anche nel caso in cui il compilatore non riuscisse a incorporare le chiamate ricorsive è improbabile in ogni caso, quindi questo è più di un argomento accademico).
Inoltre, questa soluzione è generale, nel senso che si può usare come una libreria di testa solo per richiamare le funzioni con argomenti invertiti e con il minimo sforzo: descending_print()
dovrebbe essere solo una minima involucro leggero intorno ascending_print()
.
Ecco come dovrebbe apparire come:
MAKE_REVERT_CALLABLE(ascending_print)
template<typename... Args>
void descending_print(Args&&... args)
{
revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...);
}
Quella che segue è una presentazione della realizzazione.
Primo passo: ritornando una sequenza tipo
Ecco un modo semplice per ripristinare una sequenza tipo:
#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;
};
Un piccolo programma di test:
int main()
{
static_assert(
std::is_same<
revert_type_seq<char, int, bool>::type,
std::tuple<bool, int, char>
>::value,
"Error"
);
}
E un live example.
Secondo passo: ritornando una tupla
Il passo successivo consiste nel ritornando una tupla.Data la solita macchine indici trucco:
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;
Insieme con le funzioni sopra definiti, una tupla può essere facilmente ripristinata questo modo:
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)>());
}
Ecco un semplice programma di test:
#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
}
Questo è un live example.
Terzo passo: ritornando argomenti di una funzione
Il passo finale consiste nel disimballaggio tupla quando si chiama la nostra funzione di destinazione. Ecco un altro programma di utilità generica per salvare noi un paio di righe:
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);
}
La funzione di cui sopra crea una tupla i cui elementi sono gli argomenti forniti, ma in ordine inverso. Non siamo pronti a definire il nostro obiettivo:
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)...);
}
La funzione sopra riportata (s) stampa tutti gli argomenti forniti. Ed ecco come potremmo scrivere 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)>());
}
Un semplice caso di test di nuovo:
int main()
{
ascending_print(42, 3.14, "Hello, World!");
std::cout << std::endl;
descending_print(42, 3.14, "Hello, World!");
}
E naturalmente un live example.
passo finale: semplificazione
La soluzione sopra può essere non banale comprendere, ma può essere fatta banale uso, e molto flessibile. Dato un paio di funzioni generiche:
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)...);
}
E un paio di definizioni di macro (non riuscivo a trovare un modo per creare un sovraccarico impostato per un modello di funzione, sorry):
#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()
diventa veramente facile da adattare qualsiasi funzione per essere chiamato con argomenti in ordine inverso:
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!");
}
per concludere, come di consueto, un live example.
[Questo] (http://stackoverflow.com/questions/8773426/iterating-variadic-template-arguments-in-reverse-order) potrebbe essere utile! – Carsten
@Aschratt L'ho visto, penso che il titolo della domanda sia fuorviante. – towi
L'espansione in una tupla e la generazione di indici in ordine inverso sarebbe un modo relativamente semplice, sebbene ciò induca un sovraccarico per la costruzione della tupla. – Xeo