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

6 minutos de lectura

avatar de usuario
Howard Hinant

Sección 23.3.7 Clase vector<bool> [vector.bool]el párrafo 1 establece:

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

Sin embargo, este programa falla al 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 consistente en esta especificación desde C++98. Y además noto que libc++ no ha seguido esta especificación de manera consistente desde la primera introducción de libc++.

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

avatar de usuario
Howard Hinant

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

Introducción

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

Este papel entra en algunos ejemplos específicos sobre cómo el comportamiento de vector<bool> difiere de cualquier otra instanciación de vector, dañando así el código genérico. Sin embargo, el mismo documento analiza extensamente las muy buenas propiedades de rendimiento. vector<bool> puede tener si se implementa correctamente.

Resumen: vector<bool> no es un mal contenedor. En realidad es bastante útil. Simplemente tiene un mal nombre.

De regreso const_reference

Como se introdujo anteriormente, y detallado aquíque tiene de malo vector<bool> es que se comporta de manera diferente en código genérico que en otros vector instanciaciones He aquí 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 afirmación marcada // Fires! se disparará, pero sólo cuando test se ejecuta con un vector<bool>. Cuando se ejecuta con un vector<char> (o cualquier vector además bool cuando un no predeterminado apropiado T está asignado), la prueba pasa.

La implementación de libc++ buscó minimizar los efectos negativos de tener vector<bool> se comportan de manera diferente en el código genérico. Una cosa que hizo para lograr esto es hacer vector<T>::const_reference a referencia de proxyal igual que el especificado vector<T>::reference, excepto que no puede asignar a través de él. Es decir, en libc++, vector<T>::const_reference es esencialmente un puntero al bit dentro del vectoren lugar de una copia de ese bit.

En libc++ lo anterior test pases para los dos vector<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 realmente 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 brindarle al cliente libc++ un mejor comportamiento en el código genérico, y quizás después de suficientes pruebas de campo, proponga esta extensión a un futuro estándar de C++ para mejorar toda la industria de C++.

Tal propuesta podría venir en forma de un nuevo contenedor (por ejemplo, bit_vector) que tiene la misma API que la actual vector<bool>pero con algunas mejoras como el const_reference discutido aquí. Seguido por la desaprobación (y eventual eliminación) del vector<bool> especialización. bitset también le vendría bien una pequeña mejora en este departamento, por ejemplo, añadir const_referencey un conjunto de iteradores.

Es decir, en retrospectiva bitset Es para vector<bool> (que debe ser renombrado a bit_vector — o lo que sea), como array Es para vector. Y la analogía debería ser cierta ya sea que estemos hablando o no de bool como el value_type de vector y 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. Actual demostrado positivo la experiencia de campo tiene una fuerte 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.

  • Pregunta: ¿podría/podría el reciente propuesta de borrador en los iteradores de proxy de @EricNiebler de alguna manera legitiman las extensiones libc++ y ponen vector<bool> en una base más de primera clase?

    – PlantillaRex

    13 de agosto de 2015 a las 6:07


  • Observación: Preferiría tener un vector_bool<Alloc> y un array_bool<N> para representar versiones empaquetadas (incluidos los iteradores proxy de acceso aleatorio que iteran todos los bits) de vector<bool, Alloc> y array<bool, N>. Sin embargo, bitset<N> (y es 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 bitset, con los bidireccional iteradores (iterando sobre los bits 1, en lugar de sobre todos los bits). ¿Cuáles son sus pensamientos sobre esto?

    – PlantillaRex

    13 de agosto de 2015 a las 6:14


  • Mi borrador de propuesta sobre iteradores proxy haría vector<bool> un contenedor de acceso aleatorio conforme a std. no haría vector<bool> una buena idea. 🙂 Estoy de acuerdo con Howard. Debería haberse llamado de otra manera.

    –Eric Niebler

    13 de agosto de 2015 a las 18:57

  • ¿Por qué no hay una manera de excluirse de las extensiones libc++ y obtener un comportamiento estrictamente conforme? (Ni siquiera pido que la conformidad sea la predeterminada, solo una forma de deshabilitar las extensiones de libc ++ para poder escribir código portátil). Como saben, me mordieron las extensiones de tupla libc++ en el pasado, y recientemente me mordió la extensión bitset::const_reference.

    – gnzlbg

    14 de agosto de 2015 a las 9:33


  • @gnzlbg: Se disponía de una cantidad finita de recursos económicos y temporales para el desarrollo inicial de libc++. Posteriormente, la implementación estaba condenada a no hacer felices a todos los usuarios. Dados los recursos disponibles, se realizaron compensaciones de ingeniería en un intento de maximizar la cantidad de usuarios satisfechos y maximizar el beneficio para la comunidad general de C++. Lamento tu experiencia. Observo que las extensiones de tupla con las que se encontró ahora están en el documento de trabajo actual de C++1z. En ese tema, sin saberlo, te sacrificaste para que muchos pudieran beneficiarse, y esos muchos te deben una deuda de gratitud.

    – Howard Hinant

    14/08/2015 a las 13:31

¿Ha sido útil esta solución?