¿Por qué el inicializador C-Class 11 no puede usar paréntesis?

Por ejemplo, no puedo escribir esto:

class A { vector v(12, 1); }; 

Solo puedo escribir esto:

 class A { vector v1{ 12, 1 }; vector v2 = vector(12, 1); }; 

¿Cuál es la consideración de las diferencias en el diseño del lenguaje C ++ 11?

Una posible razón es que permitir paréntesis nos llevaría de vuelta al análisis más irritante en muy poco tiempo. Considere los dos tipos a continuación:

 struct foo {}; struct bar { bar(foo const&) {} }; 

Ahora, tiene un miembro de datos de la bar de tipos que desea inicializar, por lo que lo define como

 struct A { bar B(foo()); }; 

Pero lo que has hecho arriba es declarar una función llamada B que devuelve un objeto de bar por valor, y toma un único argumento que es una función que tiene la firma foo() (devuelve un foo y no toma ningún argumento).

A juzgar por el número y la frecuencia de las preguntas formuladas en StackOverflow que tratan este tema, esto es algo que la mayoría de los progtwigdores de C ++ encuentran sorprendente e intuitivo. Agregar la nueva syntax de corchete o inicializador fue una oportunidad para evitar esta ambigüedad y comenzar con una borrón y cuenta nueva, que es probablemente la razón por la que el comité de C ++ eligió hacerlo.

 bar B{foo{}}; bar B = foo(); 

Ambas líneas arriba declaran un objeto llamado B de tipo bar , como se esperaba.


Además de las conjeturas anteriores, me gustaría señalar que estás haciendo dos cosas muy diferentes en tu ejemplo anterior.

 vector v1{ 12, 1 }; vector v2 = vector(12, 1); 

La primera línea inicializa v1 a un vector que contiene dos elementos, 12 y 1 . El segundo crea un vector v2 que contiene 12 elementos, cada uno inicializado en 1 .

Tenga cuidado con esta regla: si un tipo define un constructor que toma una lista initializer_list , entonces ese constructor siempre se considera primero cuando el inicializador para el tipo es una lista inicial arrinconada . Los otros constructores serán considerados solo si el que toma initializer_list no es viable.

El fundamento de esta elección se menciona explícitamente en la propuesta relacionada para inicializadores de miembros de datos no estáticos :

Un problema planteado en Kona con respecto al scope de los identificadores:

Durante la discusión en el Core Working Group en la reunión de septiembre ’07 en Kona, surgió una pregunta sobre el scope de los identificadores en el inicializador. ¿Queremos permitir el scope de clase con la posibilidad de búsqueda hacia adelante; o queremos exigir que los inicializadores estén bien definidos en el punto en que se analizan?

Lo que se desea

La motivación para la búsqueda del scope de clase es que nos gustaría poder poner todo en un inicializador de miembro de datos no estáticos que pudiéramos poner en un inicializador de memoria sin cambiar significativamente la semántica (inicialización directa del módulo frente a la inicialización de la copia) :

 int x(); struct S { int i; S() : i(x()) {} // currently well-formed, uses S::x() // ... static int x(); }; struct T { int i = x(); // should use T::x(), ::x() would be a surprise // ... static int x(); }; 

Problema 1:

Desafortunadamente, esto hace que los inicializadores de la forma “(lista de expresiones)” sean ambiguos en el momento en que se analiza la statement:

  struct S { int i(x); // data member with initializer // ... static int x; }; struct T { int i(x); // member function declaration // ... typedef int x; }; 

Una posible solución es confiar en la regla existente de que, si una statement puede ser un objeto o una función, entonces es una función:

  struct S { int i(j); // ill-formed...parsed as a member function, // type j looked up but not found // ... static int j; }; 

Una solución similar sería aplicar otra regla existente, actualmente utilizada solo en plantillas, que si T podría ser un tipo u otra cosa, entonces es otra cosa; y podemos usar “typename” si realmente queremos decir un tipo:

 struct S { int i(x); // unabmiguously a data member int j(typename y); // unabmiguously a member function }; 

Ambas soluciones introducen sutilezas que probablemente sean malinterpretadas por muchos usuarios (como lo demuestran las muchas preguntas en comp.lang.c ++ sobre por qué “int i ();” en el scope del bloque no declara una int inicializada por defecto) .

La solución propuesta en este documento es permitir solo los inicializadores de los formularios “= initializer-clause” y “{initializer-list}” . Eso resuelve el problema de ambigüedad en la mayoría de los casos, por ejemplo:

 HashingFunction hash_algorithm{"MD5"}; 

Aquí, no podríamos usar la forma = porque el constructor de HasningFunction es explícito. En casos especialmente difíciles, un tipo podría tener que ser mencionado dos veces. Considerar:

  vector x = 3; // error: the constructor taking an int is explicit vector x(3); // three elements default-initialized vector x{3}; // one element with the value 3 

En ese caso, debemos elegir entre las dos alternativas usando la notación apropiada:

 vector x = vector(3); // rather than vector x(3); vector x{3}; // one element with the value 3 

Problema 2:

Otro problema es que, debido a que no proponemos cambios en las reglas para inicializar los miembros de datos estáticos, agregar la palabra clave estática podría hacer que un inicializador bien formado esté mal formado:

  struct S { const int i = f(); // well-formed with forward lookup static const int j = f(); // always ill-formed for statics // ... constexpr static int f() { return 0; } }; 

Problema 3:

Un tercer problema es que la búsqueda del scope de clase podría convertir un error en tiempo de comstackción en un error en tiempo de ejecución:

 struct S { int i = j; // ill-formed without forward lookup, undefined behavior with int j = 3; }; 

(A menos que sea capturado por el comstackdor, podría ser inicializado con el valor indefinido de j.)

La propuesta:

CWG tuvo una encuesta de 6 a 3 en Kona a favor de la búsqueda de scope de clase; y eso es lo que propone este documento, con los inicializadores para los miembros de datos no estáticos limitados a los formularios “= initializer-clause” y “{initializer-list}”.

Creemos:

Problema 1: Este problema no ocurre ya que no proponemos la notación (). Las anotaciones de inicializador = y {} no sufren este problema.

Problema 2: agregar la palabra clave estática genera una serie de diferencias, siendo esta la menos importante.

Problema 3: este no es un problema nuevo, pero es el mismo problema de orden de inicialización que ya existe con los inicializadores de constructor.