Por que o vetor de libc ++ <bool> :: const_reference não é bool?

92

Seção 23.3.7 Classe vector<bool>[vector.bool], parágrafo 1 afirma:

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

No entanto, este programa falha ao compilar ao usar libc ++:

#include <vector>
#include <type_traits>

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

Além disso, observo que o padrão C ++ tem sido consistente nesta especificação desde o C ++ 98. Além disso, observo que a libc ++ não tem seguido esta especificação de forma consistente desde a primeira introdução da libc ++.

Qual é a motivação para esta não conformidade?

Howard Hinnant
fonte

Respostas:

99

A motivação para esta extensão, que é detectável por um programa em conformidade e, portanto, não conforme, é fazer com que o vector<bool>comportamento seja mais semelhante vector<char>em relação às referências (const e não).

Introdução

Desde 1998, vector<bool>tem sido ridicularizado como "não exatamente um contêiner". O LWG 96 , um dos primeiros problemas do LWG, lançou o debate. Hoje, 17 anos depois, vector<bool>permanece praticamente inalterado.

Este artigo apresenta alguns exemplos específicos de como o comportamento de vector<bool>difere de todas as outras instanciações de vector, prejudicando assim o código genérico. No entanto, o mesmo artigo discute detalhadamente as excelentes propriedades de desempenho que vector<bool>podem ter se implementadas corretamente.

Resumo : vector<bool>não é um recipiente ruim. Na verdade, é bastante útil. Ele só tem um nome ruim.

De volta a const_reference

Conforme apresentado acima e detalhado aqui , o que é ruim vector<bool>é que ele se comporta de maneira diferente no código genérico do que em outras vectorinstanciações. Aqui está um exemplo 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);
}

A especificação padrão diz que a declaração marcada // Fires!será acionada, mas apenas quando testfor executada com um vector<bool>. Quando executado com um vector<char>(ou qualquer vectoralém boolquando um não-padrão apropriado Té atribuído), o teste passa.

A implementação da libc ++ buscou minimizar os efeitos negativos de ter um vector<bool>comportamento diferente no código genérico. Uma coisa que ele fez para conseguir isso foi fazer vector<T>::const_referenceuma referência de proxy , exatamente como o especificado vector<T>::reference, exceto que você não pode atribuir por meio dele. Ou seja, no libc ++, vector<T>::const_referenceé essencialmente um ponteiro para o bit dentro do vector, em vez de uma cópia desse bit.

Em libc ++, o acima testpassa para ambos vector<char>e vector<bool>.

A que custo?

A desvantagem é que essa extensão é detectável, conforme mostrado na pergunta. No entanto, poucos programas realmente se preocupam com o tipo exato desse alias e mais programas se preocupam com o comportamento.

Qual é a motivação para esta não conformidade?

Para dar ao cliente libc ++ um comportamento melhor em código genérico, e talvez depois de testes de campo suficientes, proponha esta extensão para um futuro padrão C ++ para o aprimoramento de toda a indústria C ++.

Essa proposta pode vir na forma de um novo contêiner (por exemplo bit_vector) que tem praticamente a mesma API de hoje vector<bool>, mas com algumas atualizações, como as const_referencediscutidas aqui. Seguido pela descontinuação (e eventual remoção) da vector<bool>especialização. bitsettambém poderia usar uma pequena atualização neste departamento, por exemplo const_reference, adicionar e um conjunto de iteradores.

Ou seja, em retrospectiva bitseté para vector<bool>(que deve ser renomeado para bit_vector- ou qualquer outra coisa), como arrayé para vector. E a analogia deve ser verdadeira, quer estejamos falando ou não sobre boolcomo value_typede vectore array.

Existem vários exemplos de recursos C ++ 11 e C ++ 14 que começaram como extensões em libc ++. É assim que os padrões evoluem. A experiência de campo positiva real demonstrada exerce forte influência. O pessoal dos padrões é um grupo conservador quando se trata de alterar as especificações existentes (como deveriam ser). Adivinhar, mesmo quando você tem certeza de que está adivinhando corretamente, é uma estratégia arriscada para desenvolver um padrão reconhecido internacionalmente.

Howard Hinnant
fonte
1
Pergunta: o rascunho recente da proposta sobre iteradores de proxy por @EricNiebler poderia / iria de alguma forma legitimar as extensões libc ++ e colocar vector<bool>em uma base mais de primeira classe?
TemplateRex
Observação: Eu preferiria ter um vector_bool<Alloc>e um array_bool<N>para representar versões compactadas (incluindo iteradores de proxy de acesso aleatório iterando todos os bits) de vector<bool, Alloc>e array<bool, N>. No entanto, bitset<N>(e seu primo boost::dynamic_bitset<Alloc>) representam uma abstração diferente: a saber, versões compactadas de std::set<int>. Portanto, gostaria de ter, digamos, bit_array<N>e bit_vector<Alloc>ser os sucessores da franquia de bitset, com iteradores bidirecionais apropriados (iterando sobre os bits de 1, em vez de sobre todos os bits). Quais são seus pensamentos sobre isso?
TemplateRex
5
Meu rascunho de proposta sobre iteradores de proxy faria vector<bool>um contêiner de acesso aleatório em conformidade com o padrão. Não seria vector<bool>uma boa ideia. :-) Eu concordo com Howard. Ele deveria ter sido chamado de outra coisa.
Eric Niebler
1
Por que não existe uma maneira de cancelar as extensões libc ++ e obter um comportamento estritamente de conformidade? (Eu nem estou pedindo para tornar a conformidade o padrão, apenas uma forma de desabilitar as extensões do libc ++ para poder escrever código portátil). Como você sabe, eu fui mordido por extensões de tupla libc ++ no passado, e recentemente mordido pela extensão bitset :: const_reference.
gnzlbg
5
@gnzlbg: Uma quantidade finita de recursos econômicos e temporais estava disponível para o desenvolvimento inicial de libc ++. Posteriormente, a implementação estava condenada a não deixar todos os usuários felizes. Dados os recursos disponíveis, compensações de engenharia foram feitas na tentativa de maximizar o número de usuários satisfeitos, maximizar os benefícios para a comunidade C ++ geral. Desculpe pela sua experiência. Observo que as extensões de tupla com as quais você se deparou estão agora no documento de trabalho C ++ 1z atual. Sobre esse assunto, você sem saber se sacrificou para que muitos pudessem se beneficiar, e muitos têm uma dívida de gratidão com você.
Howard Hinnant de