c++ - retorno - tipos de funciones en javascript
¿Cómo invertir el orden de los argumentos de una función de plantilla variadica? (5)
Tengo una función de plantilla con argumentos de plantilla varargs , como este
template<typename Args...>
void ascendingPrint(Args... args) { /* ... */ }
Y quiero escribir
template<typename Args...>
void descendingPrint(Args... args) {
/* implementation using ascendingPrint()? */
}
¿Cómo invierto el orden de los argumentos del paquete de parámetros antes de pasarlo, es decir, en pseudocódigo?
template<typename Args...>
void descendingPrint(Args... args) {
ascendingPrint( reverse(args) );
}
Enfoque general y uso
El enfoque general consiste en agrupar los argumentos en una std::tuple
de referencias , explotando la maquinaria de reenvío perfecta de std::forward_as_tuple()
.
Esto significa que, en tiempo de ejecución, debe incurrir en una sobrecarga muy pequeña y sin operaciones innecesarias de copiar / mover. Además, el marco no usa recursividad (aparte de la recursión en tiempo de compilación , que es inevitable para generar índices), por lo que no hay riesgo de sobrecarga en tiempo de ejecución incluso en el caso de que el compilador no logre alinear las llamadas a funciones recursivas (lo cual es poco probable de todos modos, este es más un argumento académico).
Además, esta solución es general, ya que puede usarla como una biblioteca de solo encabezado para invocar sus funciones con argumentos invertidos y con el mínimo esfuerzo: descending_print()
debería ser solo un envoltorio mínimo y fino alrededor de ascending_print()
.
Así es como debería verse:
MAKE_REVERT_CALLABLE(ascending_print)
template<typename... Args>
void descending_print(Args&&... args)
{
revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...);
}
Lo que sigue es una presentación de la implementación.
Primer paso: revertir una secuencia de tipo
Aquí hay una manera simple de revertir una secuencia de 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 pequeño programa de prueba:
int main()
{
static_assert(
std::is_same<
revert_type_seq<char, int, bool>::type,
std::tuple<bool, int, char>
>::value,
"Error"
);
}
Y un ejemplo en vivo .
Segundo paso: revertir una tupla
El siguiente paso consiste en revertir una tupla. Teniendo en cuenta la maquinaria de trucos de índices habituales:
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;
Junto con las funciones definidas anteriormente, una tupla se puede revertir fácilmente de esta manera:
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)>());
}
Aquí hay un programa de prueba simple:
#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
}
Aquí hay un ejemplo en vivo .
Tercer paso: revertir los argumentos de una función
El paso final consiste en desempaquetar la tupla al llamar a nuestra función objetivo. Aquí hay otra utilidad genérica para guardarnos un par de líneas:
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 función anterior crea una tupla cuyos elementos son los argumentos proporcionados, pero en orden inverso. No estamos listos para definir nuestro objetivo:
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 (s) función (es) anterior (es) imprime todos los argumentos provistos. Y aquí es cómo podríamos escribir 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 caso de prueba simple de nuevo:
int main()
{
ascending_print(42, 3.14, "Hello, World!");
std::cout << std::endl;
descending_print(42, 3.14, "Hello, World!");
}
Y por supuesto un ejemplo en vivo .
Paso final: simplificación
La solución anterior puede ser no trivial de entender, pero puede ser trivial de usar y bastante flexible. Dado un par de funciones genéricas:
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)...);
}
Y un par de definiciones de macro (no pude encontrar una manera de crear un conjunto de sobrecarga para una plantilla de función, lo siento):
#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()
Resulta realmente fácil adaptar cualquier función para que se invoque con argumentos en orden 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!");
}
Para concluir, como de costumbre, un ejemplo en vivo .
¡Creo que en lugar de invertir los argumentos, puedes invertir tu lógica! Por ejemplo invertir las operaciones en argumentos.
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);
}
Salida
1 2 3 4
4 3 2 1
Aquí está el enfoque simple que mencioné en los comentarios: Generar índices en reversa y desempaquetar una tupla con eso.
// 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)>{});
}
Aquí hay una implementación recursiva de una revert<>
especializada 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);
}
Funciona con gcc-4.6 / 7/8 y clang y es probable que cumpla con los estándares ; la única parte difícil es la llamada de revert<Tn...>::apply(tn..., t, un...)
.
Sin embargo, tiene inconvenientes (como suele ocurrir en la recursividad), genera muchas instancias de plantilla de la función de destino (hinchazón del código) y no utiliza el reenvío perfecto , lo que puede ser un problema (pero tal vez podría mejorarse para usarlo). .
Mi solución admite un reenvío perfecto y no implica una recursión:
#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;
}
Ejemplo en vivo ( o incluso simplier ).
Además, los compiladores modernos pueden optimizar perfectamente todas las cosas innecesarias: https://godbolt.org/g/01Qf6w