Por que std :: initializer_list não é uma linguagem embutida?

95

Por que não há std::initializer_listuma linguagem central embutida?

Parece-me que é um recurso bastante importante do C ++ 11 e ainda não tem sua própria palavra-chave reservada (ou algo parecido).

Em vez disso, initializer_listé apenas uma classe de modelo da biblioteca padrão que tem um especial implícito mapeamento da nova sintaxe de lista de inicialização com suporte {...} que é tratada pelo compilador.

À primeira vista, essa solução é bastante hacky .

É assim que novas adições à linguagem C ++ serão implementadas: por funções implícitas de algumas classes de modelo e não pela linguagem central ?


Considere estes exemplos:

   widget<int> w = {1,2,3}; //this is how we want to use a class

por que uma nova classe foi escolhida:

   widget( std::initializer_list<T> init )

em vez de usar algo semelhante a qualquer uma dessas idéias:

   widget( T[] init, int length )  // (1)
   widget( T... init )             // (2)
   widget( std::vector<T> init )   // (3)
  1. uma matriz clássica, você provavelmente poderia adicionar const aqui e ali
  2. três pontos já existem na linguagem (var-args, agora modelos variadic), por que não reutilizar a sintaxe (e torná-la integrada )
  3. apenas um contêiner existente, poderia adicionar const e&

Todos eles já fazem parte da linguagem. Eu só escrevi minhas 3 primeiras idéias, tenho certeza que existem muitas outras abordagens.

emesx
fonte
26
O comitê de padrões odeia adicionar novas palavras-chave!
Alex Chamberlain
11
Isso eu entendo, mas há muitas possibilidades de como estender a linguagem (a palavra-chave foi apenas um exemplo )
emesx
10
std::array<T>não é mais 'parte da linguagem' do que std::initializer_list<T>. E esses não são os únicos componentes da biblioteca nos quais a linguagem depende. See new/ delete, type_info, vários tipos de exceção, size_tetc.
bames53
6
@Elmes: Eu teria sugerido const T(*)[N], porque isso se comporta de maneira muito semelhante ao modo como std::initializer_listfunciona.
Mooing Duck
1
Isso responde por que std::arrayou uma matriz de tamanho estático são alternativas menos desejáveis.
menino,

Respostas:

48

Já havia exemplos de recursos de linguagem "centrais" que retornavam tipos definidos no stdnamespace. typeidretorna std::type_infoe (talvez esticando um ponto) sizeofretorna std::size_t.

No primeiro caso, você já precisa incluir um cabeçalho padrão para usar esse recurso chamado de "linguagem central".

Agora, para listas de inicializadores, acontece que nenhuma palavra-chave é necessária para gerar o objeto, a sintaxe é chaves sensíveis ao contexto. Tirando isso, é o mesmo que type_info. Pessoalmente, não acho que a ausência de uma palavra-chave o torne "mais hacky". Um pouco mais surpreendente, talvez, mas lembre-se de que o objetivo era permitir a mesma sintaxe de inicializador com chaves que já era permitida para agregados.

Então, sim, você provavelmente pode esperar mais deste princípio de design no futuro:

  • se surgirem mais ocasiões em que seja possível introduzir novos recursos sem novas palavras-chave, o comitê irá aceitá-los.
  • se novos recursos exigirem tipos complexos, esses tipos serão colocados em std vez de integrados.

Conseqüentemente:

  • se um novo recurso requer um tipo complexo e pode ser introduzido sem novas palavras-chave, você obterá o que tem aqui, que é a sintaxe de "linguagem central" sem novas palavras-chave e que usa tipos de biblioteca de std.

O que acontece, eu acho, é que não há divisão absoluta em C ++ entre a "linguagem central" e as bibliotecas padrão. Eles são capítulos diferentes no padrão, mas cada um faz referência ao outro, e sempre foi assim.

Há outra abordagem no C ++ 11, que consiste em que lambdas introduzem objetos que possuem tipos anônimos gerados pelo compilador. Como não têm nomes, eles não estão em um namespace, certamente não std. Essa não é uma abordagem adequada para listas de inicializadores, porque você usa o nome do tipo quando escreve o construtor que aceita um.

Steve Jessop
fonte
1
Parece-me que essa divisão não é possível (mailny?) Por causa de tais papéis implícitos de tipos. type_infoe size_tsão bons argumentos .. bem, size_té apenas um typedef .. então vamos pular isso. A diferença entre type_infoe initializer_listé que o primeiro é o resultado de um operador explícito e o segundo de uma ação implícita do compilador. Também me parece que initializer_list poderia ser substituído por alguns contêineres já existentes .. ou melhor ainda: qualquer um que o usuário declare como tipo de argumento!
emesx
4
... ou pode ser a simples razão de que se você escreveu um construtor para vectorque leva um, arrayentão você poderia construir um vetor a partir de qualquer array do tipo certo, não apenas um gerado pela sintaxe da lista de inicializadores. Não tenho certeza se seria uma coisa ruim construir contêineres a partir de qualquer um array, mas não é a intenção do comitê ao introduzir a nova sintaxe.
Steve Jessop
2
@Christian: Não, std::arraynem tem construtores. O std::arraycaso é simplesmente inicialização de agregação. Além disso, convido você a se juntar a mim na sala de bate-papo Lounge <C ++>, pois essa discussão está ficando um pouco longa.
Xeo
3
@ChristianRau: Xeo significa que os elementos são copiados quando a lista de inicializadores é construída. Copiar uma lista de inicializadores não copia os elementos contidos.
Mooing Duck
2
@Christian List-initialisation não implica initializer_list. Pode ser muitas coisas, incluindo boa inicialização direta ole ou inicialização agregada. Nenhum deles envolve initializer_list (e alguns simplesmente não podem funcionar dessa forma).
R. Martinho Fernandes
42

O C ++ Standard Committee parece preferir não adicionar novas palavras-chave, provavelmente porque isso aumenta o risco de quebrar o código existente (o código legado poderia usar essa palavra-chave como o nome de uma variável, uma classe ou qualquer outra).

Além disso, me parece que definir std::initializer_listcomo um contêiner modelo é uma escolha bastante elegante: se fosse uma palavra-chave, como você acessaria seu tipo subjacente? Como você iteraria? Você também precisaria de vários novos operadores, e isso apenas o forçaria a lembrar mais nomes e mais palavras-chave para fazer as mesmas coisas que você pode fazer com contêineres padrão.

Tratar um std::initializer_listcomo qualquer outro contêiner oferece a oportunidade de escrever código genérico que funcione com qualquer uma dessas coisas.

ATUALIZAR:

Então, por que introduzir um novo tipo, em vez de usar alguma combinação dos existentes? (dos comentários)

Para começar, todos os outros contêineres têm métodos para adicionar, remover e colocar elementos, que não são desejáveis ​​para uma coleção gerada pelo compilador. A única exceção é std::array<>, que envolve um array estilo C de tamanho fixo e, portanto, permaneceria o único candidato razoável.

No entanto, como Nicol Bolas corretamente aponta nos comentários, outra diferença fundamental entre std::initializer_liste todos os outros contêineres padrão (incluindo std::array<>) é que os últimos têm semântica de valor , enquanto std::initializer_listtem semântica de referência . Copiar e std::initializer_list, por exemplo, não causará uma cópia dos elementos que ele contém.

Além disso (mais uma vez, cortesia de Nicol Bolas), ter um contêiner especial para listas de inicialização de chaves permite sobrecarregar a maneira como o usuário está realizando a inicialização.

Andy Prowl
fonte
4
Então, por que introduzir um novo tipo, em vez de usar alguma combinação dos existentes?
emesx
3
@elmes: Na verdade é mais parecido std::array. Mas std::arrayaloca memória enquanto std::initializaer_listenvolve uma matriz de tempo de compilação. Pense nisso como a diferença entre char s[] = "array";e char *s = "initializer_list";.
rodrigo
2
E o fato de ser um tipo normal torna a sobrecarga, a especialização de template, a decoração de nomes e coisas do gênero, sem problemas.
rodrigo
2
@rodrigo: std::arraynão aloca memória nenhuma, é uma planície T arr[N];, a mesma coisa que está apoiando std::initializer_list.
Xeo
6
@Xeo: T arr[N] aloca memória, talvez não no heap dinâmico, mas em outro lugar ... E o faz std::array. No entanto, um não vazio initializer_listnão pode ser construído pelo usuário, portanto, obviamente, não pode alocar memória.
rodrigo
6

Isso não é novidade. Por exemplo, for (i : some_container)depende da existência de métodos específicos ou funções autônomas em some_containerclasse. C # depende ainda mais de suas bibliotecas .NET. Na verdade, eu acho que esta é uma solução bastante elegante, porque você pode tornar suas classes compatíveis com algumas estruturas de linguagem sem complicar a especificação da linguagem.

Spook
fonte
2
métodos em classe ou autônomos begine endmétodos. Este é um pouco diferente da OMI.
emesx
3
É isso? Novamente, você tem uma construção de linguagem pura baseada na construção específica de seu código. Também pode ter sido feito introduzindo uma nova palavra-chave, por exemplo,iterable class MyClass { };
Spook
mas você pode colocar os métodos onde quiser, implementá-los como quiser .. Há alguma semelhança, concordo! Esta questão é sobre initializer_listembora
emesx
4

Na verdade, isso não é nada novo e, como muitos apontaram, essa prática existia em C ++ e, digamos, em C #.

Andrei Alexandrescu mencionou um bom ponto sobre isso: você pode pensar nisso como parte do namespace "central" imaginário, então fará mais sentido.

Então, é realmente algo como: core::initializer_list, core::size_t, core::begin(), core::end()e assim por diante. Esta é apenas uma coincidência infeliz de que o stdnamespace contém algumas construções de linguagem centrais.

Artem Tokmakov
fonte
2

Não só pode funcionar completamente na biblioteca padrão. A inclusão na biblioteca padrão não significa que o compilador não possa fazer truques inteligentes.

Embora possa não ser capaz em todos os casos, pode muito bem dizer: este tipo é bem conhecido, ou um tipo simples, vamos ignorar o initializer_liste apenas ter uma imagem da memória de qual deveria ser o valor inicializado.

Em outras palavras, int i {5};pode ser equivalente a int i(5);ou int i=5;ou mesmo intwrapper iw {5};Onde intwrapperestá uma classe de invólucro simples sobre um int com um construtor trivial tomando uminitializer_list

Paul de Vrieze
fonte
Temos exemplos reproduzíveis de compiladores que realmente executam "truques inteligentes" como este? É quase lógico como se , mas eu gostaria de ver a comprovação.
underscore_d
A ideia de otimizar compiladores é que o compilador pode transformar o código em qualquer código equivalente. C ++ em particular depende da otimização de abstrações "livres". A ideia de substituir o código da biblioteca padrão é comum (veja a lista interna do gcc gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html ).
Paul de Vrieze
Na verdade, sua ideia que int i {5}envolve qualquer um std::initializer_listestá errada. intnão tem nenhum construtor pegando std::initializer_list, então o 5é usado diretamente para construí-lo. Portanto, o exemplo principal é irrelevante; simplesmente não há otimização a ser feita. Além disso, uma vez que std::initializer_listenvolve o compilador criando e proxy um array 'imaginário', acho que pode favorecer a otimização, mas essa é a parte 'mágica' do compilador, então é diferente de se o otimizador em geral pode fazer algo inteligente com o objeto maçante contendo 2 iteradores que resultam
sublinhado_d
1

Não faz parte da linguagem central porque pode ser implementado inteiramente na biblioteca, apenas linha operator newe operator delete. Que vantagem haveria em tornar os compiladores mais complicados para compilá-lo?

Pete Becker
fonte