¿Por qué el vector de libc ++ <bool> :: const_reference no es bool?

92

La sección 23.3.7 Clase vector<bool>[vector.bool], párrafo 1, establece:

template <class Allocator> class vector<bool, Allocator> {
public:
    // types:
    typedef bool              const_reference;
    ...

Sin embargo, este programa no se puede compilar cuando se usa libc ++:

#include <vector>
#include <type_traits>

int
main()
{
    static_assert(std::is_same<std::vector<bool>::const_reference, bool>{}, "?");
}

Además, observo que el estándar C ++ ha sido coherente en esta especificación desde C ++ 98. Y además observo que libc ++ no ha seguido consistentemente esta especificación desde la primera introducción de libc ++.

¿Cuál es la motivación de esta no conformidad?

Howard Hinnant
fuente

Respuestas:

99

La motivación de esta extensión, que es detectable por un programa conforme, y por lo tanto no conforme, es hacer que se vector<bool>comporte más vector<char>con respecto a las referencias (const y de otro modo).

Introducción

Desde 1998, vector<bool>ha sido ridiculizado como "no es un contenedor". LWG 96 , uno de los primeros temas de LWG, lanzó el debate. Hoy, 17 años después, vector<bool>permanece prácticamente sin cambios.

Este artículo analiza algunos ejemplos específicos sobre cómo el comportamiento de vector<bool>difiere de cualquier otra instanciación de vector, perjudicando así el código genérico. Sin embargo, el mismo artículo analiza en detalle las excelentes propiedades de rendimiento que vector<bool>pueden tener si se implementan correctamente.

Resumen : vector<bool>no es un mal contenedor. De hecho, es bastante útil. Simplemente tiene mala fama.

De regreso const_reference

Como se presentó anteriormente y se detalla aquí , lo malo vector<bool>es que se comporta de manera diferente en el código genérico que en otrosvector instancias. Aquí hay un ejemplo concreto:

#include <cassert>
#include <vector>

template <class T>
void
test(std::vector<T>& v)
{
    using const_ref = typename std::vector<T>::const_reference;
    const std::vector<T>& cv = v;
    const_ref cr = cv[0];
    assert(cr == cv[0]);
    v[0] = 1;
    assert(true == cv[0]);
    assert(cr == cv[0]);  // Fires!
}

int
main()
{
    std::vector<char> vc(1);
    test(vc);
    std::vector<bool> vb(1);
    test(vb);
}

La especificación estándar dice que la aserción marcada // Fires!se activará, pero solo cuando testse ejecute con un vector<bool>. Cuando se ejecuta con un vector<char>(o cualquiervector además boolcuando un no predeterminado apropiado Tes asignado), la prueba pasa.

La implementación de libc ++ buscó minimizar los efectos negativos de haberse vector<bool>comportado de manera diferente en código genérico. Una cosa que hizo para lograr esto es hacer vector<T>::const_referenceuna referencia de proxy , al igual que la especificadavector<T>::reference , excepto que no se puede asignar a través de ella. Es decir, en libc ++, vector<T>::const_referencees esencialmente un puntero al bit dentro de vector, en lugar de una copia de ese bit.

En libc ++ lo anterior testpasa para ambosvector<char> y vector<bool>.

¿A que costo?

La desventaja es que esta extensión es detectable, como se muestra en la pregunta. Sin embargo, muy pocos programas se preocupan por el tipo exacto de este alias y más programas se preocupan por el comportamiento.

¿Cuál es la motivación de esta no conformidad?

Para darle al cliente libc ++ un mejor comportamiento en código genérico, y quizás después de suficientes pruebas de campo, proponga esta extensión a un futuro estándar C ++ para el mejoramiento de toda la industria C ++.

Tal propuesta podría venir en forma de un nuevo contenedor (por ejemplo bit_vector) que tenga la misma API que la actual vector<bool>, pero con algunas actualizaciones como las que se const_referencecomentan aquí. Seguido de la desaprobación (y eventual eliminación) de la vector<bool>especialización. bitsetTambién podría usar una pequeña actualización en este departamento, por ejemplo const_reference, agregar y un conjunto de iteradores.

Es decir, en retrospectiva bitsetes a vector<bool>(que debería cambiarse a bit_vector- o lo que sea), como arrayes a vector. Y la analogía debería ser cierta ya sea que estemos hablando o no boolcomo el value_typede vectory array.

Hay varios ejemplos de características de C ++ 11 y C ++ 14 que comenzaron como extensiones en libc ++. Así es como evolucionan los estándares. La experiencia de campo positiva demostrada real tiene una gran influencia. La gente de los estándares es un grupo conservador cuando se trata de cambiar las especificaciones existentes (como debería ser). Adivinar, incluso cuando esté seguro de que está adivinando correctamente, es una estrategia arriesgada para desarrollar un estándar reconocido internacionalmente.

Howard Hinnant
fuente
1
Pregunta: ¿podría / podría la reciente propuesta preliminar sobre iteradores de proxy de @EricNiebler de alguna manera legitimar las extensiones libc ++ y poner vector<bool>una base más de primera clase?
TemplateRex
Observación: Preferiría tener a vector_bool<Alloc>y an array_bool<N>para representar versiones empaquetadas (incluidos los iteradores de proxy de acceso aleatorio que iteran todos los bits) de vector<bool, Alloc>y array<bool, N>. Sin embargo, bitset<N>(y su primo boost::dynamic_bitset<Alloc>) representan una abstracción diferente: a saber, versiones empaquetadas de std::set<int>. Así que me gustaría tener, digamos, bit_array<N>y bit_vector<Alloc>ser los sucesores de la franquicia del conjunto de bits, con iteradores bidireccionales apropiados (iterando sobre los 1 bits, en lugar de sobre todos los bits). ¿Qué piensas sobre esto?
TemplateRex
5
Mi borrador de propuesta sobre iteradores de proxy haría vector<bool>un contenedor de acceso aleatorio conforme a std. No sería vector<bool>una buena idea. :-) Estoy de acuerdo con Howard. Debería haberse llamado de otra forma.
Eric Niebler
1
¿Por qué no hay forma de excluirse de las extensiones libc ++ y obtener un comportamiento estrictamente conforme? (Ni siquiera estoy pidiendo que la conformidad sea la predeterminada, solo una forma de deshabilitar las extensiones de libc ++ para poder escribir código portátil). Como saben, fui mordido por las extensiones de tupla libc ++ en el pasado, y recientemente me mordió la extensión bitset :: const_reference.
gnzlbg
5
@gnzlbg: Se dispuso de una cantidad finita de recursos económicos y temporales para el desarrollo inicial de libc ++. Posteriormente, la implementación estuvo condenada a no hacer felices a todos los usuarios. Dados los recursos disponibles, se hicieron concesiones de ingeniería en un intento de maximizar el número de usuarios satisfechos, maximizar el beneficio para la comunidad de C ++ en general. Perdón por tu experiencia. Observo que las extensiones de tupla con las que tuvo problemas ahora están en el documento de trabajo actual de C ++ 1z. En ese tema, sin saberlo, se sacrificó para que muchos pudieran beneficiarse, y esos muchos le deben una deuda de gratitud.
Howard Hinnant