c++ - initialize - ¿Qué podría salir mal si la inicialización de la lista de copia permitiera constructores explícitos?
c++ initializer list (4)
¿No es porque ''explícito'' está ahí para detener el lanzamiento implícito, y le estás pidiendo que haga un lanzamiento implícito?
¿Estaría haciendo la pregunta si ha especificado la estructura con un solo constructor de argumentos?
En el estándar de C ++, §13.3.1.7 [over.match.list], se establece lo siguiente:
En copy-list-initialization, si se elige un constructor
explicit
, la inicialización está mal formada.
Esta es la razón por la cual no podemos hacer, por ejemplo, algo como esto:
struct foo {
// explicit because it can be called with one argument
explicit foo(std::string s, int x = 0);
private:
// ...
};
void f(foo x);
f({ "answer", 42 });
(Tenga en cuenta que lo que sucede aquí no es una conversión , y tampoco sería una si el constructor estuviera "implícito". Esta es la inicialización de un objeto foo
usando su constructor directamente. Aparte de std::string
, no hay conversión aquí.)
Esto me parece perfectamente bien. No hay forma de que una conversión implícita me muerda.
Si { "answer", 42 }
puede inicializar algo más, el compilador no me traicionará y hará lo incorrecto:
struct bar {
// explicit because it can be called with one argument
explicit bar(std::string s, int x = 0);
private:
// ...
};
void f(foo x);
void f(bar x);
f({ "answer", 42 }); // error: ambiguous call
No hay problema: la llamada es ambigua, el código no se compilará, y tendré que elegir la sobrecarga explícitamente.
f(bar { "answer", 42 }); // ok
Como la prohibición está explícitamente establecida, tengo la sensación de que me falta algo aquí. Por lo que puedo ver, la lista de inicialización que escoge constructores explícitos no me parece un problema: al usar la sintaxis de inicialización de listas, el programador ya está expresando el deseo de hacer algún tipo de "conversión".
¿Qué puede salir mal? ¿Qué me estoy perdiendo?
Como yo entiendo, el propósito de la palabra clave explícita es negar el molde implícito con este constructor.
¿Entonces estás preguntando por qué el constructor explícito no se puede usar para el molde implícito? Obviamente porque el autor de ese constructor lo negó explícitamente al usar la palabra clave explícita con él. La cita del estándar que ha publicado solo indica que la palabra clave explícita también se aplica a las listas de inicializadores (no solo a los valores simples de algún tipo).
AÑADIR:
Para decir más correctamente: el propósito de la palabra clave explícita utilizada con algún constructor es dejar absolutamente en claro que este constructor se usa en algún lugar (es decir, obligando a todo el código a invocar este constructor explícitamente).
Y la instrucción IMO como f({a,b})
cuando f
es un nombre de la función no tiene nada que ver con la llamada explícita al constructor. No está del todo claro (y depende del contexto) qué constructor (y qué tipo) se utiliza aquí, por ejemplo, depende de las sobrecargas de función presentes.
Por otro lado, algo como f(SomeType(a,b))
es algo totalmente diferente: está absolutamente claro que usamos el constructor de tipo SomeType
que toma dos argumentos a,b
y que usamos la función f
sobrecarga que será lo mejor para aceptar un solo argumento de tipo SomeType
.
Entonces, algunos constructores están bien para el uso implícito como f({a,b})
y otros requieren que el hecho de su uso sea absolutamente claro para el lector, por eso los declaramos explícitos .
ADD2:
Mi punto es: a veces tiene sentido declarar explícitos a los constructores incluso si nada puede salir mal. IMO si el constructor es explícito es más una cuestión de su lógica que advertencias de cualquier tipo.
P.ej
double x = 2; // looks absolutely natural
std::complex<double> x1 = 3; // also looks absolutely natural
std::complex<double> x2 = { 5, 1 }; // also looks absolutely natural
Pero
std::vector< std::set<std::string> > seq1 = 7; // looks like nonsense
std::string str = some_allocator; // also looks stupid
Conceptualmente, la inicialización de la lista de copia es la conversión de un valor compuesto a un tipo de destino. El documento que propuso la redacción y explicó el razonamiento ya consideró el término "copia" en "inicialización de la lista de copias" como desafortunado, ya que en realidad no refleja el razonamiento real que lo sustenta. Pero se mantiene para la compatibilidad con la redacción existente. Un valor de {10, 20}
par / tupla no debería poder copiar la inicialización de una String(int size, int reserve)
, porque una cadena no es un par.
Los constructores explícitos son considerados pero prohibidos para ser utilizados. Esto tiene sentido en los casos de la siguiente manera
struct String {
explicit String(int size);
String(char const *value);
};
String s = { 0 };
0
no transmite el valor de una cadena. Por lo tanto, esto genera un error porque se consideran ambos constructores, pero se selecciona un constructor explicit
, en lugar de que el 0
se trate como una constante de puntero nulo.
Lamentablemente, esto también ocurre en la resolución de sobrecarga en todas las funciones
void print(String s);
void print(std::vector<int> numbers);
int main() { print({10}); }
Esto está mal formado también debido a una ambigüedad. Algunas personas (incluyéndome a mí) antes de que se lanzara C ++ 11 pensaron que esto era desafortunado, pero no se me ocurrió un documento que proponía un cambio al respecto (hasta donde yo sé).
Esta declaración:
En copy-list-initialization, si se elige un constructor
explicit
, la inicialización está mal formada.
significa muchas cosas. Entre ellos, significa que debe mirar constructores explícitos. Después de todo, no puede seleccionar un constructor explícito si no puede verlo. Cuando busca candidatos para convertir la lista reforzada en, debe seleccionar de todos los candidatos. Incluso los que más tarde serán ilegales.
Si la resolución de sobrecarga da como resultado que múltiples funciones sean igualmente viables, resulta en una llamada ambigua que requiere la intervención manual del usuario.