c++ - ¿Por qué es necesario allocator:: rebind cuando tenemos parámetros de plantilla de plantilla?
templates template-templates (4)
Cada clase asignadora debe tener una interfaz similar a la siguiente:
template<class T>
class allocator
{
...
template<class Other>
struct rebind { typedef allocator<Other> other; };
};
Y las clases que usan asignadores hacen algo redundante como esto:
template<class T, class Alloc = std::allocator<T> >
class vector { ... };
Pero ¿por qué es esto necesario?
En otras palabras, no podrían haber dicho simplemente:
template<class T>
class allocator { ... };
template<class T, template<class> class Alloc = std::allocator>
class vector { ... };
¿Cuál es más elegante, menos redundante y (en algunas situaciones similares) potencialmente más seguro?
¿Por qué tomaron la ruta de rebind
, lo que también causa más redundancia (es decir, tiene que decir T
dos veces)?
(Una pregunta similar se char_traits
a char_traits
y el resto ... aunque no todos se char_traits
a char_traits
, aún podrían beneficiarse de los parámetros de la plantilla de la plantilla).
Editar:
¡Pero esto no funcionará si necesita más de 1 parámetro de plantilla!
En realidad, funciona muy bien!
template<unsigned int PoolSize>
struct pool
{
template<class T>
struct allocator
{
T pool[PoolSize];
...
};
};
Ahora si el vector
solo fue definido de esta manera:
template<class T, template<class> class Alloc>
class vector { ... };
Entonces podrías decir:
typedef vector<int, pool<1>::allocator> int_vector;
Y funcionaría perfectamente bien, sin necesidad de que (redundantemente) diga int
dos veces.
Y una operación de rebind
dentro de vector
solo se convertiría en Alloc<Other>
lugar de Alloc::template rebind<Other>::other
.
Pero ¿por qué es esto necesario?
¿Qué sucede si su clase de asignador tiene más de un argumento de plantilla?
Eso es más o menos en términos de por qué generalmente se desaconseja el uso de argumentos de plantilla de plantilla, a favor de usar argumentos de plantilla normales, incluso si esto significa un poco de redundancia en el sitio de creación de instancias. En muchos casos (sin embargo, probablemente no para los asignadores), ese argumento podría no ser siempre una plantilla de clase (por ejemplo, una clase normal con funciones de miembro de plantilla).
Puede resultarle conveniente (dentro de la implementación de la clase contenedora) usar un parámetro de plantilla de plantilla solo porque simplifica parte de la sintaxis interna. Sin embargo, si el usuario tiene una plantilla de clase de argumentos múltiples como un asignador que desea usar, pero requiere que el usuario proporcione un asignador que sea una plantilla de clase de argumento único, en efecto lo forzará a crear una envoltura para casi Cualquier nuevo contexto en el que deba usar ese repartidor. Esto no solo no es escalable, también puede ser muy inconveniente de hacerlo. Y, en este punto, esa solución está lejos de ser la solución "elegante y menos redundante" que originalmente pensó que sería. Digamos que tenía un asignador con dos argumentos, ¿cuál de los siguientes es el más fácil para el usuario?
std::vector<T, my_allocator<T,Arg2> > v1;
std::vector<T, my_allocator_wrapper<Arg2>::template type > v2;
Básicamente obliga al usuario a construir muchas cosas inútiles (envoltorios, alias de plantillas, etc.) solo para satisfacer las demandas de su implementación. Requerir que el autor de una clase de asignador personalizado proporcione una plantilla de revinculación anidada (que es solo un alias de plantilla trivial) es mucho más fácil que todas las contorsiones que necesita con el enfoque alternativo.
En su enfoque, obliga al asignador a ser una plantilla con un solo parámetro, lo que podría no ser siempre el caso. En muchos casos, los asignadores pueden ser sin plantilla y la rebind
anidada puede devolver el mismo tipo de asignador. En otros casos, el asignador puede tener argumentos de plantilla adicionales. Este segundo caso es el caso de std::allocator<>
que, como todas las plantillas de la biblioteca estándar, puede tener argumentos de plantilla adicionales siempre que la implementación proporcione valores predeterminados. También tenga en cuenta que la existencia de rebind
es opcional en algunos casos, donde allocator_traits
puede usarse para obtener el tipo de rebote.
El estándar en realidad menciona que la rebind
anidada es en realidad solo un typedef con plantilla:
§17.6.3.5 / 3 Nota A: La revinculación de la plantilla de la clase miembro en la tabla anterior es efectivamente una plantilla typedef. [Nota: En general, si el nombre Asignador está enlazado a
SomeAllocator<T>
, entoncesAllocator::rebind<U>::other
es del mismo tipo queSomeAllocator<U>
, dondesomeAllocator<T>::value_type
es T ySomeAllocator<U>::value_type
es U. - nota final] Si Allocator es una instanciación de plantilla de clase del formularioSomeAllocator<T, Args>
, donde Args es cero o más argumentos de tipo, y Allocator no proporciona una plantilla de miembro de rebind, la plantilla de allocator_traits estándar utilizaSomeAllocator<U, Args>
en lugar deAllocator:: rebind<U>::other
de forma predeterminada. Para los tipos de asignador que no son instancias de plantilla del formulario anterior, no se proporciona ningún valor predeterminado.
Supongamos que quieres escribir una función tomando todo tipo de vectores.
Entonces es mucho más conveniente poder escribir.
template <class T, class A>
void f (std::vector <T, A> vec) {
// ...
}
que tener que escribir
template <class T, template <class> class A>
void f (std::vector <T, A> vec) {
// ...
}
En la mayoría de los casos, a esa función no le importa el asignador de todos modos.
Además, tenga en cuenta que no es necesario que los asignadores sean una plantilla. Podría escribir clases separadas para tipos particulares que deben asignarse.
Una forma aún más conveniente de diseñar asignadores habría sido probablemente
struct MyAllocator {
template <class T>
class Core {
// allocator code here
};
};
Entonces hubiera sido posible escribir
std::vector <int, MyAllocator> vec;
en lugar de la expresión algo engañosa
std::vector <int, MyAllocator<int> > vec;
No estoy seguro de si se permite usar el MyAllocator
anterior como asignador después de agregar un nuevo rebind
, es decir, si la siguiente es una clase de asignador válida:
struct MyAllocator {
template <class T>
class Core {
// allocator code here
};
template <class T>
struct rebind { using other=Core<T>; };
};
Un texto citado de Foundations of Algorithms en C ++ 11 , Volume 1, chap 4, p. 35:
template <typename T>
struct allocator
{
template <typename U>
using rebind = allocator<U>;
};
uso de la muestra:
allocator<int>::rebind<char> x;
En El lenguaje de programación C ++ , 4ª edición, sección 34.4.1, pág. 998, comentando el miembro de revinculación ''clásico'' en la clase asignadora predeterminada:
template<typename U>
struct rebind { using other = allocator<U>;};
Bjarne Stroustrup escribe esto:
La curiosa plantilla de reenlazado es un alias arcaico. Debería haber sido:
template<typename U> using other = allocator<U>;
Sin embargo, el asignador se definió antes de que dichos alias fueran compatibles con C ++.