c++ templates c++11 template-meta-programming generative-programming

c++ - Implementando rasgos de tipo variadico.



templates c++11 (3)

Introducción

Estoy buscando un patrón para convertir rasgos de tipo C ++ en sus contrapartes variadic . Se apreciaría una metodología para abordar el problema y los patrones de programación generativos para automatizar la tarea serían ideales.

Ejemplo

Toma lo siguiente:

std::is_same<T, U>::value;

Quiero escribir un rasgo que funcione así:

std::are_same<T1, T2, T3, T4>::value;

Enfoque actual

Es bastante straightforward implementar are_same ; Buscando una solución general, podemos encontrar una herramienta para cualquier rasgo variado que implemente la cuantificación universal :

template<template<class,class> class F, typename...Ts> struct Univ; template<template<class, class> class F, typename T, typename U, typename...Ts> struct Univ<F, T, U, Ts...> { static const int value = F<T, U>::value && Univ<F, U, Ts...>::value; }; template<template<class, class> class F, typename T> struct Univ<F, T> { static const int value = 1; };

de modo que, por ejemplo, are_same podría escribirse como

Univ<is_same,int, int, int>::value

y esto podría aplicarse al crear rasgos como are_classes , are_scalars , etc.

Generalizando

Los ajustes menores podrían dar una cuantificación existencial del fragmento anterior (reemplazando && con || ), de modo que creamos rasgos como exist_same de la siguiente manera:

Exist<is_same, int, double, float>::value

Pregunta

La generalización de la cubierta anterior en los rasgos de tipo relacionados con

  • Categorías de tipo primario
  • Categorías de tipo compuesto
  • Propiedades tipo
  • Operaciones soportadas

¿Cómo generalizaría para rasgos de tipo como el siguiente:

enable_if -> enable_if_any // enable if any clause is true enable_if_all // enalbe if all clauses are true enable_for // enable only for the type provided

El ejemplo de exist_same anterior está simplificado en exceso. ¿Alguna idea para una correcta implementación?

Hay type_traits que "devuelven" tipos modificados. ¿Alguna sugerencia para escalar esos a implementaciones para un número arbitrario de tipos?

¿Hay type_traits que se hacen para no escalar a un número arbitrario de argumentos de tipo ?


Inspirados por la excelente idea de la respuesta de Daniel Fray, incluso podemos ampliar el alcance de estos rasgos variados . Usando tuplas, podemos aplicar rasgos en colecciones de paquetes de tipo variadic en lugar de "solo" comparar un paquete variadic de tipos con un tipo de referencia.

Por ejemplo, podremos ver si los tipos int, int, int, float son los mismos tipos que int, int, int, float (¡de hecho lo son!).

Para ello, necesitamos las siguientes construcciones:

  • Tuplas y una forma de producir la cola de una tupla.
  • Una forma de extender las secuencias bool anexando (o añadiendo) valores booleanos.

TL; DR

Compilé algunos ejemplos en esta demostración en vivo .

Definición de las facilidades del rasgo variado.

Primero proporcionamos un ayudante para extender una secuencia bool un valor a la vez:

template <bool ... Bs> struct bool_sequence {}; template <bool b, typename T> struct prepend_bool_seq; template <bool b, bool ... bs> struct prepend_bool_seq<b, bool_sequence<bs...>> { typedef bool_sequence<b, bs...> type; };

Ahora un poco de lógica en secuencias bool (tomado de otras respuestas)

template <typename T> struct all_of; template <bool ... Bs> struct all_of<bool_sequence<Bs...>> : public std::is_same<bool_sequence<true, Bs...>, bool_sequence<Bs..., true>> {}; template <typename T> struct any_of; template <bool ... Bs> struct any_of<bool_sequence<Bs...>> : public std::integral_constant<bool, !all_of<bool_sequence<!Bs...>>::value> {};

Luego, definimos una plantilla de ayuda para acceder a la cola de una tupla:

namespace details { // Sentinel type to detect empty tuple tails struct null_type {}; template <typename T> struct tuple_tail; template <typename T> struct tuple_tail<std::tuple<T>> { typedef null_type type; }; template <typename T, typename ... Ts> struct tuple_tail<std::tuple<T, Ts...>> { typedef std::tuple<Ts...> type; }; }

Combinando los constructos.

Con estos ladrillos, ahora podemos definir una plantilla apply_trait para aplicar un rasgo de tipo dado en varias listas de tipos:

namespace details { template <template <typename...> class Trait, typename ... Tuples> struct apply_trait { static constexpr bool atomic_value = Trait<typename std::tuple_element<0u, Tuples>::type...>::value; typedef typename prepend_bool_seq<atomic_value, typename apply_trait<Trait, typename tuple_tail<Tuples>::type...>::type>::type type; }; template <template <typename...> class Trait, typename ... Tuples> struct apply_trait<Trait, null_type, Tuples...> { typedef bool_sequence<> type; }; }

Esta plantilla calcula de forma recursiva la secuencia bool dada por la aplicación del rasgo de manera ascendente. Ahora, con la secuencia bool resultante, podemos realizar operaciones lógicas en el resultado con los ayudantes definidos anteriormente.

A continuación, algunos ayudantes pueden reproducir la lógica de su ejemplo are_same para cualquier rasgo de tipo binario (o unario):

// Helper templates for common type traits (unary and binary) template <template <typename> class UnaryTrait, typename ... Ts> using apply_unary_trait = details::apply_trait<UnaryTrait, std::tuple<Ts...>>; template <template <typename, typename> class BinaryTrait, typename Ref, typename ... Ts> using apply_binary_trait = details::apply_trait<BinaryTrait, std::tuple<decltype(std::declval<Ts>(), std::declval<Ref>())...>, std::tuple<Ts...>>; template <template <typename, typename> class BinaryTrait, typename Ref, typename ... Ts> using apply_binary_trait_ref_last = details::apply_trait<BinaryTrait, std::tuple<Ts...>, std::tuple<decltype(std::declval<Ts>(), std::declval<Ref>())...>>;

Por ejemplo, podemos reproducir el diseño are_same que trajo para cada rasgo binario:

template <typename Ref, typename ... Ts> using are_same = all_of<typename apply_binary_trait<std::is_same, Ref, Ts...>::type>;

También podemos aplicar la lógica de rasgos en las listas. Por ejemplo, dadas dos listas de tipos, podemos querer verificar si un tipo en la primera lista es convertible a su tipo coincidente en la segunda lista:

// int is convertible to long and char const* is convertible to std::string std::cout << all_of<details::apply_trait<std::is_convertible, std::tuple<int, char const*>, std::tuple<long, std::string>::type>::value;


No entiendo completamente qué es exactamente lo que te gustaría lograr, pero los siguientes ayudantes podrían ser útiles, comenzando con bool_sequence :

#include <type_traits> // Note: std::integer_sequence is C++14, // but it''s easy to use your own version (even stripped down) // for the following purpose: template< bool... Bs > using bool_sequence = std::integer_sequence< bool, Bs... >; // Alternatively, not using C++14: template< bool... > struct bool_sequence {};

A continuación, puede verificar si todos o cualquier valor booleano o establecer con estos:

template< bool... Bs > using bool_and = std::is_same< bool_sequence< Bs... >, bool_sequence< ( Bs || true )... > >; template< bool... Bs > using bool_or = std::integral_constant< bool, !bool_and< !Bs... >::value >;

son útiles como bloques de construcción para rasgos más avanzados y especializados. Por ejemplo, podrías usarlos así:

typename< typename R, bool... Bs > // note: R first, no default :( using enable_if_any = std::enable_if< bool_or< Bs... >::value, R >; typename< typename R, bool... Bs > // note: R first, no default :( using enable_if_all = std::enable_if< bool_and< Bs... >::value, R >; typename< typename T, typename... Ts > using are_same = bool_and< std::is_same< T, Ts >::value... >;


También puede usar std::conditional para lograr enable_if_all y enable_if_any :

#include <type_traits> #include <iostream> #include <initializer_list> #include <string> namespace detail { template <typename... Conds> struct and_ : std::true_type {}; template <typename... Conds> struct or_ : std::false_type {}; template <typename Cond, typename... Conds> struct and_<Cond, Conds...> : std::conditional<Cond::value, detail::and_<Conds...>, std::false_type>::type {}; template <typename Cond, typename... Conds> struct or_<Cond, Conds...> : std::conditional<Cond::value, std::true_type, detail::and_<Conds...>>::type {}; } template <typename... T> using are_all_pod = detail::and_<std::is_pod<T>...>; template <typename... T> using any_is_pod = detail::or_<std::is_pod<T>...>; template <typename... Args, typename = typename std::enable_if<are_all_pod<Args...>::value>::type> void f(Args... args) { (void)std::initializer_list<int>{(std::cout << args << ''/n'' , 0)...}; } template <typename... Args, typename = typename std::enable_if<any_is_pod<Args...>::value>::type> void g(Args... args) { (void)std::initializer_list<int>{(std::cout << args << ''/n'' , 0)...}; } int main() { std::string s = "hello"; // non pod //f(1, 1.2, s); // this will fail because not all types are pod g(1, 1.2, s); // this compiles because there is at least one pod in argument pack }