uso signo parentesis matematicas los llaves ejemplos dentro corchetes c++ initialization c++14 language-lawyer default-constructor

c++ - signo - parentesis ejemplos



Inicialización doble del paréntesis (1)

¿Qué constructor debería llamarse en el siguiente código y por qué?

struct S { int i; S() = default; S(void *) : i{1} { ; } }; S s{{}};

Si uso clang (desde trunk), se llama al segundo.

Si el segundo constructor está comentado, entonces S{{}} sigue siendo una expresión válida, pero (creo que) move-constructor de la instancia construida por defecto de S{} se llama en el caso.

¿Por qué el constructor de conversiones tiene prioridad sobre el predeterminado en el primer caso?

La intención de tal combinación de los constructores de S es guardar su propiedad std::is_trivially_default_constructible_v< S > , excepto un conjunto finito de casos, cuando se debe inicializar de cierta manera.


Si el segundo constructor está comentado, entonces S {{}} sigue siendo una expresión válida, pero (estoy seguro) move-constructor de la instancia construida por defecto de S {} se llama en el caso.

En realidad, eso no es lo que sucede. El orden en [dcl.init.list] es:

La inicialización de lista de un objeto o referencia de tipo T se define de la siguiente manera:
- Si T es una clase agregada y la lista de inicializadores tiene un solo elemento de tipo cv U, [...]
- De lo contrario, si T es una matriz de caracteres y [...]
- De lo contrario, si T es un agregado, se realiza la inicialización agregada (8.6.1).

Una vez que elimina el constructor S(void *) , S convierte en un agregado, no tiene un constructor proporcionado por el usuario. S() = default no cuenta como proporcionado por el usuario debido a razones. La inicialización agregada de {} terminará inicializando el valor del miembro i .

¿Por qué el constructor de conversiones tiene prioridad sobre el predeterminado en el primer caso?

Con el void* restante, continuemos por la lista de viñetas:

- De lo contrario, si la lista de inicializadores no tiene elementos [...]
- De lo contrario, si T es una especialización de std :: initializer_list, [...]
- De lo contrario, si T es un tipo de clase, se consideran los constructores. Los constructores aplicables se enumeran y el mejor se elige a través de la resolución de sobrecarga (13.3, 13.3.1.7).

[over.match.list] nos da un proceso de resolución de sobrecarga de dos fases:

- Inicialmente, las funciones candidatas son los constructores de lista de inicializadores (8.6.4) de la clase T y la lista de argumentos consta de la lista de inicializadores como un único argumento.
- Si no se encuentra un constructor viable de listas de inicializadores, la resolución de sobrecarga se realiza nuevamente, donde las funciones candidatas son todos los constructores de la clase T y la lista de argumentos consta de los elementos de la lista de inicializadores.

Si la lista de inicializadores no tiene elementos y T tiene un constructor predeterminado, la primera fase se omite.

S no tiene ningún constructor de lista de inicializadores, por lo tanto vamos al segundo punto y enumeramos todos los constructores con la lista de argumentos de {} . Tenemos múltiples constructores viables:

S(S const& ); S(S&& ); S(void *);

Las secuencias de conversión se definen en [over.ics.list]:

De lo contrario, si el parámetro es una clase no agregada X y la resolución de sobrecarga por 13.3.1.7 elige un único mejor constructor C de X para realizar la inicialización de un objeto de tipo X desde la lista de inicializadores de argumentos:
- Si C no es un constructor de lista de inicializadores y la lista de inicializadores tiene un único elemento de tipo cv U, [...] - De lo contrario, la secuencia de conversión implícita es una secuencia de conversión definida por el usuario con la segunda secuencia de conversión estándar una identidad conversión

y

De lo contrario, si el tipo de parámetro no es una clase: [...] - si la lista de inicializadores no tiene elementos, la secuencia de conversión implícita es la conversión de identidad .

Es decir, los constructores S(S&& ) y S(S const& ) son secuencias de conversión definidas por el usuario más conversión de identidad. Pero S(void *) es solo una conversión de identidad.

Pero, [over.best.ics] tiene esta regla adicional:

Sin embargo, si el objetivo es
- el primer parámetro de un constructor o
- el parámetro de objeto implícito de una función de conversión definida por el usuario
y el constructor o la función de conversión definida por el usuario es un candidato por
- 13.3.1.3, cuando [...]
- 13.3.1.4, 13.3.1.5 o 13.3.1.6 (en todos los casos), o
- la segunda fase de 13.3.1.7 cuando la lista de inicializadores tiene exactamente un elemento que a su vez es una lista de inicializadores, y el objetivo es el primer parámetro de un constructor de clase X , y la conversión es a X o referencia a (posiblemente cv- calificado) X ,

secuencias de conversión definidas por el usuario no son consideradas.

Esto excluye de la consideración S(S const&) y S(S&& ) como candidatos - son precisamente este caso - siendo el objetivo el primer parámetro del constructor como resultado de la segunda fase de [over.match.list] y el objetivo siendo una referencia a S posiblemente calificado para S , y dicha secuencia de conversión sería definida por el usuario.

Por lo tanto, el único candidato restante es S(void *) , por lo que es trivialmente el mejor candidato viable.