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 { };