c++ templates sfinae enable-if

c++ - ¿Cómo escribir un rasgo de tipo `is_container` o` is_vector`?



templates sfinae (10)

¿Por qué no hacer algo así para is_container?

template <typename Container> struct is_container : std::false_type { }; template <typename... Ts> struct is_container<std::list<Ts...> > : std::true_type { }; template <typename... Ts> struct is_container<std::vector<Ts...> > : std::true_type { }; // ...

De esta forma, los usuarios pueden agregar sus propios contenedores mediante una especialización parcial. En cuanto a is_vector et-al, solo use la especialización parcial como hice anteriormente, pero limítelo a un solo tipo de contenedor, no a muchos.

¿Es posible escribir un rasgo de tipo cuyo valor sea verdadero para todas las estructuras de STL comunes (p. Ej., vector , set , map , ...)?

Para comenzar, me gustaría escribir un rasgo de tipo que sea verdadero para un vector y falso de lo contrario. Intenté esto, pero no compila:

template<class T, typename Enable = void> struct is_vector { static bool const value = false; }; template<class T, class U> struct is_vector<T, typename boost::enable_if<boost::is_same<T, std::vector<U> > >::type> { static bool const value = true; };

El mensaje de error es template parameters not used in partial specialization: U


Avanzando rápidamente a 2018 y C ++ 17, me atreví a mejorar con la respuesta de @Frank

// clang++ prog.cc -Wall -Wextra -std=c++17 #include <iostream> #include <vector> namespace dbj { template<class T> struct is_vector { using type = T ; constexpr static bool value = false; }; template<class T> struct is_vector<std::vector<T>> { using type = std::vector<T> ; constexpr static bool value = true; }; // and the two "olbigatory" aliases template< typename T> inline constexpr bool is_vector_v = is_vector<T>::value ; template< typename T> using is_vector_t = typename is_vector<T>::type ; } // dbj int main() { using namespace dbj; std::cout << std::boolalpha; std::cout << is_vector_v<std::vector<int>> << std::endl ; std::cout << is_vector_v<int> << std::endl ; } /* Created 2018 by [email protected] */

La "prueba del pudín" . Hay mejores formas de hacerlo, pero esto funciona para std::vector .


Dirías que debería ser más simple que eso ...

template <typename T, typename _ = void> struct is_vector { static const bool value = false; }; template <typename T> struct is_vector< T, typename enable_if< is_same<T, std::vector< typename T::value_type, typename T::allocator_type > >::value >::type > { static const bool value = true; };

... Pero no estoy seguro de si eso es más simple o no.

En C ++ 11 puedes usar alias de tipo (creo, no probado):

template <typename T> using is_vector = is_same<T, std::vector< typename T::value_type, typename T::allocator_type > >;

El problema con su enfoque es que el tipo U no es deducible en el contexto donde se usa.


En nuestro proyecto todavía no logramos migrar al compilador compatible con C ++ 11, por lo que para type_traits de contenedores de objetos tuve que escribir un simple boost boost helper:

template<typename Cont> struct is_std_container: boost::false_type {}; template<typename T, typename A> struct is_std_container<std::vector<T,A> >: boost::true_type {}; template<typename T, typename A> struct is_std_container<std::list<T,A> >: boost::true_type {}; template<typename T, typename A> struct is_std_container<std::deque<T,A> >: boost::true_type {}; template<typename K, typename C, typename A> struct is_std_container<std::set<K,C,A> >: boost::true_type {}; template<typename K, typename T, typename C, typename A> struct is_std_container<std::map<K,T,C,A> >: boost::true_type {}; template<typename Cont> struct is_std_sequence: boost::false_type {}; template<typename T, typename A> struct is_std_sequence<std::vector<T,A> >: boost::true_type {}; template<typename T, typename A> struct is_std_sequence<std::list<T,A> >: boost::true_type {}; template<typename T, typename A> struct is_std_sequence<std::deque<T,A> >: boost::true_type {};


En realidad, después de algunos intentos de prueba y error, encontré que es bastante simple:

template<class T> struct is_vector<std::vector<T> > { static bool const value = true; };

Todavía me gustaría saber cómo escribir un is_container más general. ¿Debo enumerar todos los tipos a mano?


La forma en que me gusta detectar si algo es un contenedor es buscar las funciones de los miembros data() y size() . Me gusta esto:

template <typename T, typename = void> struct is_container : std::false_type {}; template <typename T> struct is_container<T , std::void_t<decltype(std::declval<T>().data()) , decltype(std::declval<T>().size())>> : std::true_type {};


Mientras que las otras respuestas aquí que tratan de adivinar si una clase es un contenedor o no pueden funcionar para usted, me gustaría presentarle la alternativa de nombrar el tipo que quiere que sea verdadero. Puede usar esto para construir tipos de rasgos is_(something) arbitrarios.

template<class T> struct is_container : public std::false_type {}; template<class T, class Alloc> struct is_container<std::vector<T, Alloc>> : public std::true_type {}; template<class K, class T, class Comp, class Alloc> struct is_container<std::map<K, T, Comp, Alloc>> : public std::true_type {};

Y así.

Deberá incluir <type_traits> y las clases que agregue a sus reglas.


Mira, otra solución basada en SFINAE para detectar contenedores tipo STL:

template<typename T, typename _ = void> struct is_container : std::false_type {}; template<typename... Ts> struct is_container_helper {}; template<typename T> struct is_container< T, std::conditional_t< false, is_container_helper< typename T::value_type, typename T::size_type, typename T::allocator_type, typename T::iterator, typename T::const_iterator, decltype(std::declval<T>().size()), decltype(std::declval<T>().begin()), decltype(std::declval<T>().end()), decltype(std::declval<T>().cbegin()), decltype(std::declval<T>().cend()) >, void > > : public std::true_type {};

Por supuesto, puede cambiar los métodos y tipos que se verificarán.

Si desea detectar solo contenedores STL (significa std::vector , std::list , etc.), debe hacer algo como this .


Si también desea que funcione para const std :: vector, puede usar lo siguiente:

namespace local { template<typename T, typename _ = void> struct isVector: std::false_type { }; template<typename T> struct isVector<T, typename std::enable_if< std::is_same<typename std::decay<T>::type, std::vector<typename std::decay<T>::type::value_type, typename std::decay<T>::type::allocator_type> >::value>::type> : std::true_type { }; } TEST(TypeTraitTest, testIsVector) { ASSERT_TRUE(local::isVector<std::vector<int>>::value); ASSERT_TRUE(local::isVector<const std::vector<int>>::value); ASSERT_FALSE(local::isVector<std::list<int>>::value); ASSERT_FALSE(local::isVector<int>::value); std::vector<uint8_t> output; std::vector<uint8_t> &output2 = output; EXPECT_TRUE(core::isVector<decltype(output)>::value); EXPECT_TRUE(core::isVector<decltype(output2)>::value); }

Sin la llamada std :: remove_cv, el segundo ASSERT_TRUE fallaría. Pero, por supuesto, esto depende de tus necesidades. Aquí, según las especificaciones, std :: is_same comprueba si const y volátil también coinciden.


template <typename T> struct is_container { template < typename U, typename I = typename U::const_iterator > static int8_t test(U* u); template <typename U> static int16_t test(...); enum { value = sizeof test <typename std::remove_cv<T>::type> (0) == 1 }; }; template<typename T, size_t N> struct is_container <std::array<T,N>> : std::true_type { };