Pergunta

Por alguma razão pensei que C++ 0x fosse permitido std::initializer_list como argumento de função para funções que esperam tipos que podem ser construídos a partir deles, por exemplo std::vector.Mas, aparentemente, não funciona.Este é apenas meu compilador ou nunca funcionará?É por causa de possíveis problemas de resolução de sobrecarga?

#include <string>
#include <vector>

void function(std::vector<std::string> vec)
{
}

int main()
{
    // ok
    std::vector<std::string> vec {"hello", "world", "test"};

    // error: could not convert '{"hello", "world", "test"}' to 'std::vector...'
    function( {"hello", "world", "test"} );
}
Foi útil?

Solução

O GCC tem um bug.A Norma torna isso válido.Ver:

Observe que existem dois lados disso

  • Como e que inicialização é feita em geral?
  • Como a inicialização é usada durante a resolução de sobrecarga e qual o custo disso?

A primeira pergunta é respondida na seção 8.5.A segunda pergunta é respondida na seção 13.3.Por exemplo, a vinculação de referência é tratada em 8.5.3 e 13.3.3.1.4, enquanto a inicialização da lista é tratada em 8.5.4 e 13.3.3.1.5.

8.5/14,16:

A inicialização que ocorre no formato

T x = a;

bem como na passagem de argumentos, o retorno da função, o lançamento de uma exceção (15.1), o tratamento de uma exceção (15.3) e a inicialização de membro agregado (8.5.1) são chamados de inicialização de cópia.
.
.
A semântica dos inicializadores é a seguinte[...]:Se o inicializador for uma lista de inicialização entre chaves, o objeto será inicializado em lista (8.5.4).

Ao considerar o candidato function, o compilador verá uma lista de inicializadores (que ainda não tem tipo - é apenas uma construção gramatical!) como argumento e um std::vector<std::string> como parâmetro de function.Para descobrir qual é o custo da conversão e se pode convertê-los em contexto de sobrecarga, 13.3.3.1/5 diz

13.3.3.1.5/1:

Quando um argumento é uma lista de inicializadores (8.5.4), não é uma expressão e regras especiais se aplicam para convertê-lo em um tipo de parâmetro.

13.3.3.1.5/3:

Caso contrário, se o parâmetro for uma classe X não agregada e a resolução de sobrecarga de acordo com 13.3.1.7 escolher um único melhor construtor de X para executar a inicialização de um objeto do tipo X da lista de inicializadores de argumentos, a sequência de conversão implícita será um usuário- sequência de conversão definida.Conversões definidas pelo usuário são permitidas para conversão dos elementos da lista do inicializador para os tipos de parâmetros do construtor, exceto conforme observado em 13.3.3.1.

A classe não agregada X é std::vector<std::string>, e descobrirei o melhor construtor abaixo.A última regra nos permite usar conversões definidas pelo usuário em casos como os seguintes:

struct A { A(std::string); A(A const&); };
void f(A);
int main() { f({"hello"}); }

Temos permissão para converter a string literal em std::string, mesmo que isso precise de uma conversão definida pelo usuário.Contudo, aponta restrições de outro parágrafo.O que 13.3.3.1 dizer?

13.3.3.1/4, que é o parágrafo responsável por proibir múltiplas conversões definidas pelo usuário.Veremos apenas as inicializações de lista:

No entanto, ao considerar o argumento de uma função de conversão definida pelo usuário [(ou construtor)] que é candidata por [...] 13.3.1.7 ao passar a lista de inicializadores como um único argumento ou quando a lista de inicializadores tem exatamente um elemento e uma conversão para alguma classe X ou referência a (possivelmente qualificada por cv) X é considerada para o primeiro parâmetro de um construtor de X, ou [...], apenas sequências de conversão padrão e sequências de conversão de reticências são permitidas.

Observe que esta é uma restrição importante:Se não fosse por isso, o acima poderia usar o construtor de cópia para estabelecer uma sequência de conversão igualmente boa, e a inicialização seria ambígua.(observe a confusão potencial de "A ou B e C" nessa regra:Significa dizer "(A ou B) e C" - então estamos restritos apenas ao tentar converter por um construtor de X tendo um parâmetro do tipo X).

Somos delegados a 13.3.1.7 para coletar os construtores que podemos usar para fazer essa conversão.Vamos abordar este parágrafo do lado geral, começando por 8.5 que nos delegou 8.5.4:

8.5.4/1:

A inicialização de lista pode ocorrer em contextos de inicialização direta ou de inicialização de cópia;inicialização de lista em um contexto de inicialização direta é chamada inicialização de lista direta e a inicialização de lista em um contexto de inicialização de cópia é chamada inicialização da lista de cópias.

8.5.4/2:

Um construtor é um construtor da lista de inicializadores se seu primeiro parâmetro for do tipo std::initializer_list<E> ou referência a possivelmente qualificado por cv std::initializer_list<E> para algum tipo E, e não há outros parâmetros ou todos os outros parâmetros têm argumentos padrão (8.3.6).

8.5.4/3:

A inicialização de lista de um objeto ou referência do tipo T é definida da seguinte forma:[...] Caso contrário, se T for um tipo de classe, os construtores serão considerados.Se T tiver um construtor de lista de inicializadores, a lista de argumentos consistirá na lista de inicializadores como um único argumento;caso contrário, a lista de argumentos consiste nos elementos da lista de inicializadores.Os construtores aplicáveis ​​são enumerados (13.3.1.7) e o melhor é escolhido através da resolução de sobrecarga (13.3).

Neste momento, T é o tipo de classe std::vector<std::string>.Temos um argumento (que ainda não possui um tipo!Estamos apenas no contexto de ter uma lista de inicializadores gramaticais).Os construtores são enumerados a partir de 13.3.1.7:

[...] Se T tem um construtor de lista de inicializadores (8.5.4), a lista de argumentos consiste na lista de inicializadores como um único argumento;caso contrário, a lista de argumentos consiste nos elementos da lista de inicializadores.Para inicialização de lista de cópias, as funções candidatas são todas os construtores de T.Entretanto, se um construtor explícito for escolhido, a inicialização será mal formada.

Consideraremos apenas a lista de inicializadores de std::vector como único candidato, pois já sabemos que os outros não vencerão ou não se enquadrarão no argumento.Possui a seguinte assinatura:

vector(initializer_list<std::string>, const Allocator& = Allocator());

Agora, as regras para converter uma lista de inicializadores em um std::initializer_list<T> (para categorizar o custo da conversão de argumento/parâmetro) são enumerados em 13.3.3.1.5:

Quando um argumento é uma lista de inicializadores (8.5.4), não é uma expressão e regras especiais se aplicam para convertê-lo em um tipo de parâmetro.[...] Se o tipo de parâmetro for std::initializer_list<X> e todos os elementos da lista inicializadora podem ser convertidos implicitamente em X, a sequência de conversão implícita é a pior conversão necessária para converter um elemento da lista em X. Esta conversão pode ser uma conversão definida pelo usuário mesmo no contexto de uma chamada para um construtor de lista de inicializadores.

Agora, a lista de inicializadores será convertida com sucesso e a sequência de conversão é uma conversão definida pelo usuário (de char const[N] para std::string).Como isso é feito está detalhado em 8.5.4 de novo:

Caso contrário, se T é uma especialização de std::initializer_list<E>, um objeto inicializador_list é construído conforme descrito abaixo e usado para inicializar o objeto de acordo com as regras para inicialização de um objeto de uma classe do mesmo tipo (8.5).(...)

Ver 8.5.4/4 como é feita essa etapa final :)

Outras dicas

Parece funcionar desta maneira:

function( {std::string("hello"), std::string("world"), std::string("test")} );

Talvez seja um bug do compilador, mas talvez você esteja pedindo muitas conversões implícitas.

De fora, não tenho certeza, mas suspeito que o que está acontecendo aqui é que a conversão para um Initializer_list é uma conversão, e convertê -la em vetor é outra conversão. Se for esse o caso, você está excedendo o limite de apenas uma conversão implícita ...

Este é um bug do compilador ou seu compilador não suporta std :: Initializer_list. Testado no GCC 4.5.1 e ele compila bem.

Você precisa especificar o tipo do seu inicializador_list

function(std::initializer_list<std::string>{"hello", "world", "test"} );

Boa sorte

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top