Por que não há std::initializer_list
uma 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)
- uma matriz clássica, você provavelmente poderia adicionar
const
aqui e ali - três pontos já existem na linguagem (var-args, agora modelos variadic), por que não reutilizar a sintaxe (e torná-la integrada )
- 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.
fonte
std::array<T>
não é mais 'parte da linguagem' do questd::initializer_list<T>
. E esses não são os únicos componentes da biblioteca nos quais a linguagem depende. Seenew
/delete
,type_info
, vários tipos de exceção,size_t
etc.const T(*)[N]
, porque isso se comporta de maneira muito semelhante ao modo comostd::initializer_list
funciona.std::array
ou uma matriz de tamanho estático são alternativas menos desejáveis.Respostas:
Já havia exemplos de recursos de linguagem "centrais" que retornavam tipos definidos no
std
namespace.typeid
retornastd::type_info
e (talvez esticando um ponto)sizeof
retornastd::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:
std
vez de integrados.Conseqüentemente:
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.fonte
type_info
esize_t
são bons argumentos .. bem,size_t
é apenas um typedef .. então vamos pular isso. A diferença entretype_info
einitializer_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 queinitializer_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!vector
que leva um,array
entã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 umarray
, mas não é a intenção do comitê ao introduzir a nova sintaxe.std::array
nem tem construtores. Ostd::array
caso é 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.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_list
como 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_list
como qualquer outro contêiner oferece a oportunidade de escrever código genérico que funcione com qualquer uma dessas coisas.ATUALIZAR:
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_list
e todos os outros contêineres padrão (incluindostd::array<>
) é que os últimos têm semântica de valor , enquantostd::initializer_list
tem semântica de referência . Copiar estd::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.
fonte
std::array
. Masstd::array
aloca memória enquantostd::initializaer_list
envolve uma matriz de tempo de compilação. Pense nisso como a diferença entrechar s[] = "array";
echar *s = "initializer_list";
.std::array
não aloca memória nenhuma, é uma planícieT arr[N];
, a mesma coisa que está apoiandostd::initializer_list
.T arr[N]
aloca memória, talvez não no heap dinâmico, mas em outro lugar ... E o fazstd::array
. No entanto, um não vazioinitializer_list
não pode ser construído pelo usuário, portanto, obviamente, não pode alocar memória.Isso não é novidade. Por exemplo,
for (i : some_container)
depende da existência de métodos específicos ou funções autônomas emsome_container
classe. 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.fonte
begin
eend
métodos. Este é um pouco diferente da OMI.iterable class MyClass { };
initializer_list
emboraNa 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 ostd
namespace contém algumas construções de linguagem centrais.fonte
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_list
e apenas ter uma imagem da memória de qual deveria ser o valor inicializado.Em outras palavras,
int i {5};
pode ser equivalente aint i(5);
ouint i=5;
ou mesmointwrapper iw {5};
Ondeintwrapper
está uma classe de invólucro simples sobre um int com um construtor trivial tomando uminitializer_list
fonte
int i {5}
envolve qualquer umstd::initializer_list
está errada.int
não tem nenhum construtor pegandostd::initializer_list
, então o5
é usado diretamente para construí-lo. Portanto, o exemplo principal é irrelevante; simplesmente não há otimização a ser feita. Além disso, uma vez questd::initializer_list
envolve 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 resultamNão faz parte da linguagem central porque pode ser implementado inteiramente na biblioteca, apenas linha
operator new
eoperator delete
. Que vantagem haveria em tornar os compiladores mais complicados para compilá-lo?fonte