Pergunta - veja Compiler Explorer
Se eu criar um std::vector<int>
e inicializá-lo de duas maneiras, ambas chamarão o std::initializer_list
construtor.
std::vector<int> v1{1, 2, 3}; // Calls vector(initializer_list< int >)
std::vector<int> v2 = {1, 2, 3}; // Calls vector(initializer_list< int >)
Mas se eu usar uma classe que converte implicitamente de int
:
struct Imp { Imp(int) {} };
Então o segundo método de inicialização chama o construtor que recebe um par de iteradores.
std::vector<Imp> v3{1, 2, 3}; // Calls vector(initializer_list< Imp >)
std::vector<Imp> v4 = {1, 2, 3}; // Calls vector(const int*, const int* ) ???
Isso só acontece usando GCC 14.2 . Ao usar Clang 19.1 ou MSVC 19.40 , ambos v3
e v4
chamam o initializer_list
construtor. O nível de otimização não faz diferença
Por que a = {}
sintaxe chama vector(const int*, const int*)
o GCC?
Investigação adicional
Tentei criar meu próprio modelo de classe que tem ambos os construtores:
template <typename T>
struct Custom {
Custom(std::initializer_list<T>) {}
Custom(const int*, const int*) {}
};
Agora, tanto a sintaxe {}
e = {}
chamam o initializer_list
construtor no GCC.
Custom<Imp> v5{1, 2, 3}; // Calls Custom(initializer_list< Imp >)
Custom<Imp> v6 = {1, 2, 3}; // Calls Custom(initializer_list< Imp >)
Onde as coisas ficam realmente confusas é se eu me especializo std::vector
para meu tipo de conversão implícita Imp
. Então há três casos:
Caso 1 - Fornecerinitializer_list
apenas ctor
template <>
struct std::vector<Imp> {
vector(initializer_list<Imp>) {}
};
Resultado - Ambas as sintaxes chamam o ctor fornecido
std::vector<Imp> v7{1, 2, 3}; // Calls vector(initializer_list< Imp >)
std::vector<Imp> v8 = {1, 2, 3}; // Calls vector(initializer_list< Imp >)
Caso 2 - Fornecerconst int*
apenas ctor
template <>
struct std::vector<Imp> {
vector(const int*, const int*) {}
};
Resultado - Ambas as sintaxes falham na compilação (nenhum ctor correspondente)
std::vector<Imp> v9{1, 2, 3}; // Error - no matching ctor
std::vector<Imp> vA = {1, 2, 3}; // Error - no matching ctor
Caso 3 - Forneça ambos os ctors
template <>
struct std::vector<Imp> {
vector(initializer_list<Imp>) {}
vector(const int*, const int*) {}
};
Resultado - A{}
sintaxe chama oinitializer_list
ctor, enquanto a= {}
sintaxe chama oconst int*
ctor
std::vector<Imp> vB{1, 2, 3}; // Calls vector(initializer_list< Imp >)
std::vector<Imp> vC = {1, 2, 3}; // Calls vector(const int*, const int*) ???
É aqui que estou perdido.
No caso 3 , como fornecer o initializer_list
construtor permite que o const int*
construtor seja chamado?
EDITAR
Perguntaram como eu sei qual construtor está sendo chamado. Isso começou quando eu estava olhando quando os construtores copy vs. move são chamados. std::initializer_list
não pode ser movido de. Mas aqui estão alguns exemplos adicionais, com prints.
Exemplo 1 - Usando um tipo autônomo
struct Imp { Imp(int) {} };
template <typename T>
struct Custom {
Custom(std::initializer_list<T>) { std::printf("initializer_list<T>\n"); }
Custom(const int*, const int*) { std::printf("const int*, const int*\n"); }
};
int main(int argc, char *argv[]) {
Custom<Imp> v{1, 2, 3};
Custom<Imp> w = {1, 2, 3};
Impressões:
initializer_list<T>
initializer_list<T>
Exemplo 2 - Usando uma especialização std::vector
struct Imp { Imp(int) {} };
template <>
struct std::vector<Imp> {
vector(initializer_list<Imp>) { std::printf("initializer_list<T>\n"); }
vector(const int*, const int*) { std::printf("const int*, const int*\n"); }
};
int main(int argc, char *argv[]) {
std::vector<Imp> v{1, 2, 3};
std::vector<Imp> w = {1, 2, 3};
Impressões:
initializer_list<T>
const int*, const int*
A única diferença entre os dois exemplos é que um é uma especialização std:: e o outro não.
Por que isso mudaria qual sobrecarga de ctor é chamada?
Parabéns, você acabou de encontrar uma otimização secreta feita pelo GCC!
Desde o GCC 13, o GCC reescreve certas inicializações de lista (que devem chamar o
initializer_list
construtor) para usar o construtor iterador-par em vez disso. Isso tem a intenção de contornar a ineficiência ao construir umvector<string>
com uma lista de literais de string. Veja o bug 105838 do GCC .Esse comportamento é descrito no código-fonte do GCC .
O motivo pelo qual essa otimização é aplicada a ,
std::vector<T> v = {1, 2, 3}
mas não astd::vector<T> v{1, 2, 3}
, provavelmente é porque a inicialização direta é representada de forma diferente no GCC, o que faz com que ela não seja detectada por essa otimização.