¿La estructura con miembro de referencia tiene una representación de objeto única?

7 minutos de lectura

Avatar de usuario de Evg
Evg

Esta respuesta planteó la siguiente pregunta.

Supongamos que tenemos un simple

struct S {
    int& i;
}

Internamente (en GCC y Clang, al menos) S contiene sólo un puntero a un inty

static_assert(sizeof(int*) == 8);
static_assert(sizeof(S)    == 8);

Lo hace S tener una representación de objeto única? GCC y Clang no están de acuerdo*:

static_assert( std::has_unique_object_representations_v<int*>);
static_assert(!std::has_unique_object_representations_v<S>);    // GCC
static_assert( std::has_unique_object_representations_v<S>);    // Clang

¿Qué compilador está aquí y por qué?

* El desacuerdo entre GCC y Clang fue notado por idclev 463035818.

  • MSVC está de acuerdo con GCC.

    – erorika

    3 de julio de 2020 a las 11:58

  • @eerorika ¡Lo cual, en sí mismo, es algo inusual y notable! 🙂

    – Adrián Mole

    3 de julio de 2020 a las 11:59

  • @AdrianMole Bueno, en este caso tenían que estar de acuerdo con al menos otro compilador 🙂

    – erorika

    3 de julio de 2020 a las 12:01

  • @eerorika: Podrían haber sido fieles a la tradición y simplemente no implementar has_unique_object_representations_v en absoluto ;).

    – MSalters

    3 de julio de 2020 a las 12:23

  • Las referencias son solo una sintaxis alternativa para los punteros. Los miembros de referencia no tienen sentido y su especificación e implementación son errores. Es mucho más fácil prohibir miembros de referencia de su base de código que perder el tiempo razonando al respecto.

    – KevinZ

    6 de julio de 2020 a las 2:56

avatar de usuario de eerorika
erorika

En primer lugar, las referencias no son objetos. Los objetos se especifican en [intro.object] y referencias en [dcl.ref].

Los subobjetos son objetos ([intro.object]). Por lo tanto, los miembros de referencia no son subobjetos y, por lo tanto, una clase que contiene solo miembros de referencia (y no bases) no tiene subobjetos (aunque tenga miembros de datos).

[meta.unary.prop]

Se cumplirá la condición predicada para una especialización de plantilla has_unique_object_representations si y solo si:

  • T es trivialmente copiable, y
  • dos objetos cualquiera del tipo T con el mismo valor tienen la misma representación de objetodonde se considera que dos objetos del tipo de clase matriz o no unión tienen el mismo valor si sus respectivas secuencias de subobjetos directos tienen los mismos valores

La secuencia de subobjetos está vacía y, por lo tanto, es igual a otra secuencia vacía y, por lo tanto, a todos los objetos de tipo S tienen el “mismo valor”2 de acuerdo con esta regla.

Sin embargo, los objetos que se refieren a objetos diferentes necesariamente tendrán una representación de objeto diferente. Por lo tanto, el segundo requisito es no1 satisfecho

Por lo tanto, la representación del objeto no es única, Clang es técnicamente incorrecto y GCC y MSVC (que tiene el mismo resultado que GCC) son correctos.



esto se ha convertido1 un poco fuera de tema si concluimos que el segundo requisito no se cumple, pero: ¿Es S trivialmente copiable?

static_assert(std::is_trivially_copyable_v<S>);

Pasa tanto en Clang como en GCC, pero según MSVC, S es no trivialmente copiable. Entonces, ¿cuál es la correcta?

[class.copy.ctor]

Un constructor de copiar/mover para la clase X es trivial si no lo proporciona el usuario y si:

  • la clase X no tiene funciones virtuales ([class.virtual]) y sin clases base virtuales ([class.mi]), y
  • el constructor seleccionado para copiar/mover cada subobjeto directo de la clase base es trivial, y
  • para cada miembro de datos no estáticos de X que sea del tipo de clase (o matriz de los mismos), el constructor seleccionado para copiar/mover ese miembro es trivial;

Todos estos están satisfechos. Por lo tanto S tiene un constructor de copiar/mover trivial.

[class.prop]

Una clase trivialmente copiable es una clase:

  • que tiene al menos uno constructor de copia elegible, constructor de movimiento, operador de asignación de copia, o mover operador de asignación ([special], [class.copy.ctor], [class.copy.assign]),
  • donde cada constructor de copia elegible, constructor de movimiento, operador de asignación de copia y operador de asignación de movimiento es trivial, y
  • que tiene un destructor trivial, no eliminado ([class.dtor]).

Todos están satisfechos y por lo tanto S es trivialmente copiable, y el rasgo de tipo MSVC es incorrecto al afirmar lo contrario.


1 Editar: originalmente obtuve la conclusión al revés.

2 Si los miembros de datos de referencia debería ser ignorado o no al considerar el “valor” de un objeto de clase es, en mi opinión, discutible. Este tecnicismo de ignorarlos podría potencialmente considerarse un defecto en el estándar.

  • Tienes la lógica vacía al revés: si la referencia no es un subobjeto, entonces todos los objetos del tipo de clase “tienen el mismo valor”, a pesar de que pueden referirse a diferentes ints (y seguramente tienen diferente representaciones de objetos)!

    – Davis arenque

    3 de julio de 2020 a las 15:56


avatar de usuario de dfrib
dfrib

Esta es una interpretación intencional de Clang.

Tenga en cuenta que Clang elige explícitamente su enfoque basado en comentarios de Richard Smithincluso sabiendo que GCC rechazó (en el contexto del OP) std::has_unique_object_representations_v<S> y señalando este comportamiento de GCC como un posible error [emphasis mine]:

erichkeane Las referencias no se pueden copiar de manera trivial, por lo que evitarán que la estructura tenga una representación de objeto única.

herrero Eso suena como el comportamiento incorrecto para mí. Si dos estructuras tienen referencias que se unen al mismo objeto, entonces tienen la misma representación de objetopor lo que la estructura tiene representaciones de objetos únicas.

erichkeane No lo pensé de esa manera… TENDRÉ en cuenta que GCC rechaza las referencias en su implementación, pero eso podría ser un error de su parte.

herrero […] Así que creo que las referencias, como punteros, siempre se debe considerar que tienen representaciones de objetos únicas cuando se consideran miembros de objetos de tipo de clase. (Pero __has_unique_object_representations(T&) todavía debería volver false porque T& no es un tipo copiable trivialmente, aunque una clase que contiene un T& puede ser.)

Como señaló @idclev 463035818, tanto Clang como GCC de acuerdo en que S es trivialmente copiablelo que significa que su desacuerdo radica en si dos objetos de tipo (trivialmente copiable) S con el mismo valor tienen la misma representación de objeto. Para obtener una respuesta a esto último, consulte el excelente argumento de @eerorika (Clang es técnicamente incorrecto, mientras que el pasaje estándar relevante es discutible).

  • Las referencias no son trivialmente copiables, pero eso no afecta S. El que una clase sea copiable trivialmente depende solo de sus miembros de tipo de clase, y int& no es un tipo de clase. Consulte stackoverflow.com/a/30097049/15416

    – MSalters

    3 de julio de 2020 a las 12:21

  • fwiw, ambos están de acuerdo en que S es trivialmente copiable: godbolt.org/z/8oj8sQ

    – 463035818_no_es_un_número

    3 de julio de 2020 a las 12:23

  • @eerorika Gracias por el aviso. Tu respuesta actualizada dice “Por lo tanto Clang es técnicamente correcto y GCC y MSVC (que tiene el mismo resultado que GCC) son incorrectos”., mientras que su último comentario indica que Clang está realmente equivocado. ¿Clang está realmente equivocado?

    – dfrib

    3 de julio de 2020 a las 16:25

  • @eerorika Ya veo, y una muy buena respuesta tuya. Gracias de nuevo por ellos pronto.

    – dfrib

    3 de julio de 2020 a las 16:31

  • @dfri Eliminé los comentarios. Tengo los compiladores todos mezclados en el cerebro 😀

    – erorika

    3 de julio de 2020 a las 16:33

Avatar de usuario de MSalters
MSalters

S es trivialmente copiable, porque no tiene miembros de tipo de clase, ni funciones miembro declaradas por el usuario. Esto no se discute; como señala idclev 463035818, ambos compiladores están de acuerdo en que std::is_trivially_copyable_v<S>==true

Entonces la pregunta se reduce a si dos objetos S idénticos son bit a bit idéntico. Como ambas implementaciones eligen representar las referencias como punteros (una elección válida), std::has_unique_object_representations_v<S> tiene que coincidir std::has_unique_object_representations_v<int*>. Por lo tanto, GCC está mal.

¿Ha sido útil esta solución?