c++ - tutorial - qué es un fragmento de contenedor en tag manager
plantilla de plantilla c++; Funciona con un tipo de contenedor arbitrario, ¿cómo definirlo? (3)
Aquí está la versión más reciente y ampliada de esta respuesta y una mejora significativa sobre la respuesta de Sabastian.
La idea es definir todos los rasgos de los contenedores STL. Desafortunadamente, esto se complica muy rápido y, afortunadamente, muchas personas han trabajado para afinar este código. Estos rasgos son reutilizables, así que simplemente copie y pegue el código siguiente en el archivo llamado type_utils.hpp (no dude en cambiar estos nombres):
//put this in type_utils.hpp
#ifndef commn_utils_type_utils_hpp
#define commn_utils_type_utils_hpp
#include <type_traits>
#include <valarray>
namespace common_utils { namespace type_utils {
//from: https://raw.githubusercontent.com/louisdx/cxx-prettyprint/master/prettyprint.hpp
//also see https://gist.github.com/louisdx/1076849
namespace detail
{
// SFINAE type trait to detect whether T::const_iterator exists.
struct sfinae_base
{
using yes = char;
using no = yes[2];
};
template <typename T>
struct has_const_iterator : private sfinae_base
{
private:
template <typename C> static yes & test(typename C::const_iterator*);
template <typename C> static no & test(...);
public:
static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
using type = T;
void dummy(); //for GCC to supress -Wctor-dtor-privacy
};
template <typename T>
struct has_begin_end : private sfinae_base
{
private:
template <typename C>
static yes & f(typename std::enable_if<
std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::begin)),
typename C::const_iterator(C::*)() const>::value>::type *);
template <typename C> static no & f(...);
template <typename C>
static yes & g(typename std::enable_if<
std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::end)),
typename C::const_iterator(C::*)() const>::value, void>::type*);
template <typename C> static no & g(...);
public:
static bool const beg_value = sizeof(f<T>(nullptr)) == sizeof(yes);
static bool const end_value = sizeof(g<T>(nullptr)) == sizeof(yes);
void dummy(); //for GCC to supress -Wctor-dtor-privacy
};
} // namespace detail
// Basic is_container template; specialize to derive from std::true_type for all desired container types
template <typename T>
struct is_container : public std::integral_constant<bool,
detail::has_const_iterator<T>::value &&
detail::has_begin_end<T>::beg_value &&
detail::has_begin_end<T>::end_value> { };
template <typename T, std::size_t N>
struct is_container<T[N]> : std::true_type { };
template <std::size_t N>
struct is_container<char[N]> : std::false_type { };
template <typename T>
struct is_container<std::valarray<T>> : std::true_type { };
template <typename T1, typename T2>
struct is_container<std::pair<T1, T2>> : std::true_type { };
template <typename ...Args>
struct is_container<std::tuple<Args...>> : std::true_type { };
}} //namespace
#endif
Ahora puede usar estos rasgos para asegurarse de que nuestro código solo acepte tipos de contenedores. Por ejemplo, puede implementar la función de adición que agrega un vector a otro de esta manera:
#include "type_utils.hpp"
template<typename Container>
static typename std::enable_if<type_utils::is_container<Container>::value, void>::type
append(Container& to, const Container& from)
{
using std::begin;
using std::end;
to.insert(end(to), begin(from), end(from));
}
Observe que estoy usando begin () y end () del espacio de nombres estándar solo para estar seguro de que tenemos un comportamiento de iterador. Para más explicación ver mi blog .
Bien, simple pregunta de plantilla. Digamos que defino mi clase de plantilla algo como esto:
template<typename T>
class foo {
public:
foo(T const& first, T const& second) : first(first), second(second) {}
template<typename C>
void bar(C& container, T const& baz) {
//...
}
private:
T first;
T second;
}
La pregunta es acerca de mi función de barra ... Necesito que sea capaz de usar un contenedor estándar de algún tipo, razón por la cual incluí la parte de plantilla / nombre de letra C, para definir ese tipo de contenedor. Pero aparentemente no es la forma correcta de hacerlo, ya que mi clase de prueba se queja de que:
error: ''bar'' no fue declarado en este alcance
Entonces, ¿cómo voy a implementar la función de mi barra de manera adecuada? Es decir, como una función de mi clase de plantilla, con un tipo de contenedor arbitrario ... el resto de mi clase de plantilla funciona bien (tiene otras funciones que no dan como resultado un error), es solo una función la que es problemática.
EDIT: Bueno, entonces la función específica (barra) es una función eraseInRange, que borra todos los elementos en un rango específico:
void eraseInRange(C& container, T const& firstElement, T const& secondElement) {...}
Y un ejemplo de cómo se usaría sería:
eraseInRange(v, 7, 19);
donde v es un vector en este caso.
EDIT 2: tonto yo! Se suponía que debía declarar la función fuera de mi clase, no en ella ... un error bastante frustrante de estar haciendo. De todos modos, gracias a todos por la ayuda, aunque el problema fue un poco diferente, la información me ayudó a construir la función, ya que después de encontrar mi problema original, tuve otros errores agradables. ¡Así que gracias!
Haga la plantilla con plantilla en un parámetro de plantilla de plantilla:
template <template <typename, typename...> class Container>
void bar(const Container<T> & c, const T & t)
{
//
}
Si no tiene C ++ 11, entonces no puede usar plantillas variadas, y tiene que proporcionar tantos parámetros de plantilla como tome su contenedor. Por ejemplo, para un contenedor de secuencia es posible que necesite dos:
template <template <typename, typename> class Container, typename Alloc>
void bar(const Container<T, Alloc> & c, const T & t);
O, si solo desea permitir asignadores que son ellos mismos instancias de plantilla:
template <template <typename, typename> class Container, template <typename> class Alloc>
void bar(const Container<T, Alloc<T> > & c, const T & t);
Como sugerí en los comentarios, personalmente preferiría hacer de todo el contenedor un tipo de plantilla y utilizar rasgos para verificar si es válido. Algo como esto:
template <typename Container>
typename std::enable_if<std::is_same<typename Container::value_type, T>::value, void>::type
bar(const Container & c, const T & t);
Esto es más flexible ya que el contenedor ahora puede ser cualquier cosa que exponga el tipo de miembro value_type
. Se pueden concebir rasgos más sofisticados para verificar funciones de los miembros e iteradores; por ejemplo, la impresora bonita implementa algunos de esos.
Solución de rasgos.
No generalice más de lo necesario, y no menos.
En algunos casos, es posible que esa solución no sea suficiente, ya que coincidirá con cualquier plantilla con dicha firma (por ejemplo, shared_ptr
), en cuyo caso podría utilizar type_traits
, muy parecido duck-typing (las plantillas son tipografía de pato en general).
#include <type_traits>
// Helper to determine whether there''s a const_iterator for T.
template<typename T>
struct has_const_iterator
{
private:
template<typename C> static char test(typename C::const_iterator*);
template<typename C> static int test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
// bar() is defined for Containers that define const_iterator as well
// as value_type.
template <typename Container>
typename std::enable_if<has_const_iterator<Container>::value,
void>::type
bar(const Container &c, typename Container::value_type const & t)
{
// Note: no extra check needed for value_type, the check comes for
// free in the function signature already.
}
template <typename T>
class DoesNotHaveConstIterator {};
#include <vector>
int main () {
std::vector<float> c;
bar (c, 1.2f);
DoesNotHaveConstIterator<float> b;
bar (b, 1.2f); // correctly fails to compile
}
Una buena plantilla generalmente no restringe artificialmente el tipo de tipos para los que son válidos (¿por qué deberían hacerlo?). Pero imagine que en el ejemplo anterior necesita tener acceso a un const_iterator
objetos, luego puede usar SFINAE y type_traits para poner esas restricciones en su función.
O simplemente como lo hace la biblioteca estándar
No generalice más de lo necesario, y no menos.
template <typename Iter>
void bar (Iter it, Iter end) {
for (; it!=end; ++it) { /*...*/ }
}
#include <vector>
int main () {
std::vector<float> c;
bar (c.begin(), c.end());
}
Para más ejemplos de este tipo, mira en <algorithm>
.
La fuerza de este enfoque es su simplicidad y se basa en conceptos como ForwardIterator . Incluso funcionará para matrices. Si desea informar errores directamente en la firma, puede combinarlos con rasgos.
Contenedores std
con firma como std::vector
( no recomendado )
La solución más simple ya está aproximada por Kerrek SB, aunque no es válida en C ++. La variante corregida va así:
#include <memory> // for std::allocator
template <template <typename, typename> class Container,
typename Value,
typename Allocator=std::allocator<Value> >
void bar(const Container<Value, Allocator> & c, const Value & t)
{
//
}
Sin embargo : esto solo funcionará para contenedores que tienen exactamente dos argumentos de tipo de plantilla, por lo que fallará miserablemente para std::map
(gracias a Luc Danton).
Cualquier tipo de argumentos de plantilla secundaria ( no recomendado )
La versión corregida para cualquier recuento de parámetros secundarios es la siguiente:
#include <memory> // for std::allocator<>
template <template <typename, typename...> class Container,
typename Value,
typename... AddParams >
void bar(const Container<Value, AddParams...> & c, const Value & t)
{
//
}
template <typename T>
class OneParameterVector {};
#include <vector>
int main () {
OneParameterVector<float> b;
bar (b, 1.2f);
std::vector<float> c;
bar (c, 1.2f);
}
Sin embargo : esto seguirá fallando para los contenedores que no sean de plantilla (gracias a Luc Danton).