2011-02-10 16 views
5

El siguiente código representa un contenedor basado en std :: vectorPlantillas y STL

template <typename Item> 
struct TList 
{ 
    typedef std::vector <Item> Type; 
}; 


template <typename Item> 
class List 
{ 
private 
      typename TList <Item>::Type items; 
    .... 
} 

int main() 
{ 
    List <Object> list; 
} 

¿Es posible crear plantillas de std :: vector y crear un contenedor en general, algo por el estilo?

template <typename Item, typename stl_container> 
struct TList 
{ 
    typedef stl_container<Item>; 
}; 

donde stl_container representa std :: vector, std :: list, std :: set ...? Me gustaría elegir el tipo de contenedor en el momento de la creación.

List <Object, std::vector> list; //vector of objects, not a real code 
List <Object, std::vector> list; //list of objects, not a real code 

Gracias por sus respuestas ...

pregunta Actualizado:

He probado el siguiente código pero hay errores:

#include <vector> 
template <typename Item, typename Container> 
struct TList 
{ 
    typedef typename Container <Item>::type type; //Error C2059: syntax error : '<', Error C2238: unexpected token(s) preceding '; 
}; 


template <typename T> 
struct vector_container 
{ 
    typedef std::vector<T> type; 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
TList <int, vector_container> v; 
TList <int, map_container> m; 
} 
+0

¿Quién utilizaría esta clase? ¿Por qué 'List' no puede usar' typedef vector/list/set items'? ¿Cuál sería el propósito de una clase que, dado un tipo de contenedor y un tipo de valor, simplemente los junte? – UncleBens

+0

Re: la edición, todavía no veo por qué no puede escribir 'List > list;' etc – UncleBens

Respuesta

5

sí y no.

puede utilizar un parámetro de plantilla plantilla, por ejemplo,

template <typename Item, template <typename> class Container> 
struct TList { /* ... */ }; 

Sin embargo, en general, los parámetros de plantilla plantilla no son particularmente útiles debido a que el número y los tipos de los parámetros de plantilla tienen que coincidir. Por lo tanto, lo anterior no coincidiría std::vector ya que en realidad tiene dos parámetros de plantilla: una para el tipo de valor y otro para el asignador. Un parámetro de plantilla de plantilla no puede aprovechar ningún argumento de plantilla predeterminado.

Para poder utilizar la plantilla std::vector como argumento, TList tendría que ser declarado como:

template <typename Item, template <typename, typename> class Container> 
struct TList { /* ... */ }; 

Sin embargo, con esta plantilla, no sería capaz de utilizar la plantilla como un std::map argumento, ya que tiene cuatro parámetros de plantilla: los tipos de clave y valor, el tipo de imputación, y el tipo de comparación.

Por lo general, es mucho más fácil evitar los parámetros de plantilla plantilla debido a esta falta de flexibilidad.

+0

¿Podría reemplazar 'template class Container' con simplemente' typename Container' y acceder al tipo contenido en el contenedor usando 'Container :: value_type'? –

+0

@Billy: Claro; cada contenedor tiene un typedef 'value_type'. Sin embargo, eso no le permitirá _crear_ el contenedor, que es lo que el OP está preguntando. –

+0

@James: Ah - Ya veo. Parece que el OP necesita algunos iteradores entonces :) –

0

¿Es posible templamentar std :: vector y crear un contenedor general, algo así?

No. Usted tendría que crear plantillas de la función u objeto utilizando el contenedor - no se podía crear plantillas del propio recipiente.

Por ejemplo. considerar un típico std::find:

template<class InputIterator, class T> 
InputIterator find (InputIterator first, InputIterator last, const T& value) 
{ 
    for (;first!=last; first++) if (*first==value) break; 
    return first; 
} 

Esto funciona para cualquier recipiente, pero no necesita una tempalte con el contenedor en absoluto.

Además, dado que parece que lo que está tratando de hacer es crear un código contenedor independiente, puede comprar o pedir prestado una copia de Scott Meyers' Effective STL y leer el Artículo 2: Cuidado con la ilusión de código independiente del contenedor.

+0

¿No son los algoritmos que toman los iteradores, en lugar de los contenedores/rangos en parte la causa de la imposibilidad de un código independiente del contenedor? Es * posible * escribir una función de búsqueda genérica, que también funciona de manera eficiente para conjunto y mapa, pero no con esta firma. – UncleBens

+0

@UncleBens: Sí, el elemento en STL efectivo no recomienda que alguien intente utilizar solo las características que proporcionan todos los contenedores STL, para que puedan desconectarse. (Meyers es mejor para explicar que yo) Si puedes templantar un algoritmo por medio de una plantilla, entonces, por supuesto, adelante.(Pero es probable que necesite algunas especializaciones de plantilla si desea que sea más eficiente tanto para la secuencia como para los contenedores asociativos;)) –

+0

En relación con el elemento 2: cuidado con la ilusión del código independiente del contenedor, la mayoría de los algoritmos no STL bajo ese espíritu? – kirakun

2

así, puede cortarlo con una macro:

template <typename T, typename stl_container = std::vector<T> > 
struct TList 
{ 
    typedef stl_container Type; 
}; 

#define TLIST(T, C) TList<T, C<T> > 

TList<int> foo; 
TList<int, std::list<int> > bar; 
TLIST(int, std::list) baz; 
+0

¿Por qué quieres matar a tantos cachorros? : P –

+0

Porque soy un amante de los gatos ;-). Pero si, esto es un truco. – Tim

+1

No sé si votar o no votar. +1. –

11

Sí, pero no directamente:

template <typename Item, template <typename> class Container> 
struct TList 
{ 
    typedef typename Container<Item>::type type; 
}; 

Entonces podrá definir diferentes políticas de contenedores:

template <typename T> 
struct vector_container 
{ 
    typedef std::vector<T> type; 
}; 

template <typename T> 
struct map_container 
{ 
    typedef std::map<T, std::string> type; 
}; 

TList<int, vector_container> v; 
TList<int, map_container> m; 

Un poco detallado, sin embargo. * Para hacer las cosas directamente, necesitarías tomar the route described by James, pero como él nota esto es en última instancia muy inflexible.

Sin embargo, con C++ 0x podemos hacer esto muy bien:

#include <map> 
#include <vector> 

template <typename Item, 
      template <typename...> class Container, typename... Args> 
struct TList 
{ 
    // Args lets the user specify additional explicit template arguments 
    Container<Item, Args...> storage; 
}; 

int main() 
{ 
    TList<int, std::vector> v; 
    TList<int, std::map, float> m; 
} 

perfecto. Desafortunadamente no hay forma de reproducir esto en C++ 03, excepto que a través de las clases de directivas de indirección se presentan como se describió anteriormente.


* Quiero hacer hincapié en que por "Un poco prolijo" quiero decir "esto es poco ortodoxo". La solución correcta para su problema es lo que hace la biblioteca estándar, as Jerry explains. Que acaba de dejar al usuario de su adaptador de contenedores especificar todo el tipo de contenedor directamente:

template <typename Item, typename Container = std::vector<Item>> 
struct TList 
{}; 

Pero esto deja un gran problema: ¿qué pasa si no quiero el tipo de valor de que el contenedor sea Item pero something_else<Item>? En otras palabras, ¿cómo puedo cambiar el tipo de valor de un contenedor existente a otra cosa? En su caso, no es así, no siga leyendo, pero en el caso que lo hagamos, queremos volver a vincular un contenedor.

Por desgracia para nosotros, los contenedores no tienen esta funcionalidad, aunque asignadores hacen:

template <typename T> 
struct allocator 
{ 
    template <typename U> 
    struct rebind 
    { 
     typedef allocator<U> type; 
    }; 

    // ... 
}; 

Esto nos permite obtener una allocator<U> dieron un allocator<T>. ¿Cómo podemos hacer lo mismo con los contenedores sin esta utilidad intrusiva? En C++ 0x, es fácil:

template <typename T, typename Container> 
struct rebind; // not defined 

template <typename T, typename Container, typename... Args> 
struct rebind<T, Container<Args...>> 
{ 
    // assumes the rest are filled with defaults** 
    typedef Container<T> type; 
}; 

Dado std::vector<int>, podemos realizar rebind<float, std::vector<int>>::type, por ejemplo. A diferencia del anterior C++ 0x solución, ésta puede ser emulado en C++ con las macros y 03 iteración ..


** Nota Este mecanismo se puede hacer mucho más potente, como especificar qué argumentos para mantener , que volver a enlazar, que volver a enlazar antes de usar como argumentos, etc., pero eso se deja como un ejercicio para el lector. :)

+1

Buena idea. ¿Hay algún problema que no se pueda resolver con una capa adicional de indirección? –

+0

@Juegos: No. Incluso puedo ver mi frente si uso un espejo. – GManNickG

+0

jaja, eso está bien. Y los comentarios son graciosos. +1. –

6

Estoy un poco confundido por qué algunas personas muy inteligentes (y competentes) dicen que no.

A menos que haya leído mal su pregunta, lo que está tratando de lograr es prácticamente idéntico a los "adaptadores de contenedor" en la biblioteca estándar. Cada uno proporciona una interfaz para algún tipo de contenedor subyacente, con el tipo de contenedor que se usará como un parámetro de plantilla (con un valor predeterminado).

Por ejemplo, un std::stack utiliza algún otro contenedor (p. Ej., std::deque, std::list o std::vector) para contener los objetos, y std::stack solo proporciona una interfaz simplificada/restringida para cuando solo quiere usar operaciones de pila. El contenedor subyacente que será utilizado por std::stack se proporciona como un parámetro de plantilla. Así es como el código se ve en la norma:

namespace std { 
    template <class T, class Container = deque<T> > 
    class stack { 
    public: 
     typedef typename Container::value_type value_type; 
     typedef typename Container::size_type size_type; 
     typedef Container      container_type; 
    protected: 
     Container c; 
    public: 
     explicit stack(const Container& = Container()); 
     bool empty() const    { return c.empty(); } 
     size_type size() const   { return c.size(); } 
     value_type& top()    { return c.back(); } 
     const value_type& top() const { return c.back(); } 
     void push(const value_type& x) { c.push_back(x); } 
     void pop()      { c.pop_back(); } 
    }; 
} 

Por supuesto, tal vez he apenas entendido mal la pregunta - si es así, pido disculpas de antemano a las personas con las que estoy (más o menos) en desacuerdo.

+0

Jerry: El OP quiere código escrito como 'MyClass ' para poder crear un 'std :: vector '. En el ejemplo 'std :: stack', el typename pasado no es' std :: deque', es 'std :: deque ' - no es una plantilla de clase, sino una clase de plantilla. Es decir, su código daría como resultado que el OP tenga que hacer 'MyClass >'. –

+0

@Billy O'Neal: He vuelto a leer la pregunta, y todavía no puedo encontrar dónde dice que eso es lo que quiere. Tal vez no dormí lo suficiente anoche o algo así ... –

+0

Creo que el objetivo de la pregunta era conseguir el contenedor sin pasar todo su tipo en el parámetro de la plantilla. (Por ejemplo, para que pueda hacer el valor tipo 'secret_type '.) Pero creo que su respuesta también es importante ya que deberíamos resolver problemas, y no solo responder preguntas, y así es como la mayoría de nosotros lo haría. – GManNickG

-1

Puede usar los parámetros de plantilla de plantilla como otros han mencionado aquí. La dificultad principal con esto no es que los tipos de contenedor diferentes tengan parámetros de plantilla diferentes, sino que el estándar permite que los contenedores estándar, como vector, tengan parámetros de plantilla además de los necesarios documentados.

Usted puede evitar esto al proporcionar sus propios tipos de subclases que aceptan los parámetros de plantilla adecuados y dejar que cualquier accesorio (que han de tener valores por defecto) se llenarán en mi la ejecución:

template < typename T > struct simple_vector : std::vector<T> {}; 

O puede utilizar el idioma de typedef con plantilla:

template < typename T > struct retrieve_vector { typedef std::vector<T> type; }; 
+3

[El estándar C++ no permite que una implementación tenga parámetros de plantilla opcionales adicionales.] (Http://stackoverflow.com/questions/1469743/standard-library-containers-with-additional-optional-template-parameters) –

+0

I downvote porque heredas de contenedores estándar. No tienen un destructor virtual, por lo que es peligroso utilizar la clase 'simple_vector ' en lugares donde esperaría un 'vector '; los usuarios de la clase podrían (y lo harán) equivocarse y dejar que se llame 'vector :: ~ vector' en lugar de' simple_vector :: ~ simple_vector'. –

+0

No en este caso Alexandre. Tómese un momento o dos para pensarlo. O más largo... –