una tipos que programacion otro incluir hacer ficheros crear como cabecera archivos archivo c++ c++11 lambda language-lawyer one-definition-rule

c++ - tipos - ¿El uso de una lambda en los archivos de encabezado puede violar el ODR?



que es una cabecera en programacion (2)

Esto se reduce a si el tipo de lambda difiere o no entre las unidades de traducción. Si lo hace, puede afectar la deducción de argumentos de plantilla y potencialmente causar que se invoquen diferentes funciones, en lo que se entiende como definiciones consistentes. Eso violaría la ODR (ver más abajo).

Sin embargo, eso no está destinado. De hecho, este problema ya ha sido abordado hace un tiempo por el problema central 765 , que nombra específicamente las funciones en línea con enlaces externos, como f :

7.1.2 [dcl.fct.spec] el párrafo 4 especifica que las variables estáticas locales y los literales de cadena que aparecen en el cuerpo de una función en línea con enlace externo deben ser las mismas entidades en cada unidad de traducción del programa. Sin embargo, no se dice nada acerca de si los tipos locales también deben ser iguales.

Aunque un programa conforme siempre podría haber determinado esto mediante el uso de typeid, los cambios recientes a C ++ (que permiten tipos locales como argumentos de tipo de plantilla, clases de cierre de expresiones lambda ) hacen que esta pregunta sea más apremiante.

Notas de la reunión de julio de 2009:

Los tipos están destinados a ser los mismos.

Ahora, la resolución incorporó la siguiente redacción en [dcl.fct.spec]/4 :

Un tipo definido dentro del cuerpo de una función en extern inline es el mismo tipo en cada unidad de traducción.

(Nota: MSVC aún no se refiere a la redacción anterior, aunque podría hacerlo en la próxima versión ).

Las lambdas dentro de los cuerpos de tales funciones son, por lo tanto, seguras, ya que la definición del tipo de cierre está realmente en el alcance del bloque ( [expr.prim.lambda]/3 ).
Por lo tanto, múltiples definiciones de f siempre estuvieron bien definidas.

Esta resolución ciertamente no cubre todos los escenarios, ya que hay muchos más tipos de entidades con enlaces externos que pueden hacer uso de lambdas, plantillas de funciones en particular, esto debería estar cubierto por otro tema central.
Mientras tanto, Itanium ya contiene reglas apropiadas para garantizar que tales tipos de lambdas coincidan en más situaciones, por lo tanto, Clang y GCC ya deberían comportarse principalmente como se esperaba.

A continuación se detalla por qué los diferentes tipos de cierre son una violación de ODR. Considere las viñetas (6.2) y (6.4) en [basic.def.odr]/6 :

Puede haber más de una definición de [...]. Dada una entidad llamada D definida en más de una unidad de traducción, cada definición de D consistirá en la misma secuencia de tokens; y

(6.2) - en cada definición de D , los nombres correspondientes, buscados de acuerdo con [basic.lookup], se referirán a una entidad definida dentro de la definición de D , o se referirán a la misma entidad, después de la resolución de sobrecarga ([over. match]) y después de la coincidencia de la especialización parcial de plantilla ([temp.over]), [...]; y

(6.4) - en cada definición de D , los operadores sobrecargados a los que se hace referencia, las llamadas implícitas a funciones de conversión, constructores , nuevas funciones de operador y funciones de borrado de operador, se referirán a la misma función, oa una función definida dentro de la definición de D ; [...]

Lo que esto significa efectivamente es que todas las funciones llamadas en la definición de la entidad serán las mismas en todas las unidades de traducción, o se han definido dentro de su definición , como las clases locales y sus miembros. Es decir, el uso de un lambda per se no es problemático, pero pasarlo a plantillas de funciones sí lo es, ya que se definen fuera de la definición.

En su ejemplo con C , el tipo de cierre se define dentro de la clase (cuyo alcance es el más pequeño). Si el tipo de cierre difiere en dos TU, que el estándar puede implicar involuntariamente con la unicidad de un tipo de cierre, el constructor crea instancias y llama a diferentes especializaciones de la plantilla de constructor de function , violando (6.4) en la cita anterior.

¿Se puede escribir lo siguiente en un archivo de encabezado:

inline void f () { std::function<void ()> func = [] {}; }

o

class C { std::function<void ()> func = [] {}; C () {} };

Supongo que en cada archivo fuente, el tipo de lambda puede ser diferente y, por lo tanto, el tipo contenido en std::function (los resultados de target_type serán diferentes).

¿Es esta una violación de ODR ( regla de una definición ), a pesar de parecer un patrón común y algo razonable? ¿La segunda muestra viola el ODR cada vez o solo si al menos un constructor está en un archivo de encabezado?


ACTUALIZADO

Después de todo, estoy de acuerdo con la respuesta de @Columbo, pero quiero agregar los cinco centavos prácticos :)

Aunque la violación de ODR suena peligrosa, en realidad no es un problema grave en este caso particular. Las clases lambda creadas en diferentes TU son equivalentes, excepto sus typeids. Por lo tanto, a menos que tenga que lidiar con el typeid de una lambda definida por el encabezado (o un tipo que depende de la lambda), está a salvo.

Ahora, cuando la violación de ODR se informa como un error, existe una gran posibilidad de que se solucione en compiladores que tienen el problema, por ejemplo, MSVC y probablemente otros que no siguen el Itanium ABI. Tenga en cuenta que los compiladores conformes con Itanium ABI (p. Ej., Gcc y clang) ya están produciendo el código correcto de ODR para lambdas definidas por encabezado.