Estoy jugando con [[no_unique_address]]
adentro c++20
.
En el ejemplo de cppreference tenemos un tipo Empty
y un tipo vacíosZ
struct Empty {}; // empty class
struct Z {
char c;
[[no_unique_address]] Empty e1, e2;
};
Aparentemente, el tamaño de Z
tiene que ser al menos 2
porque los tipos de e1
y e2
son iguales.
Sin embargo, realmente quiero tener Z
con el tamaño 1
. Esto me hizo pensar, ¿qué pasa con el ajuste Empty
en alguna clase de contenedor con un parámetro de plantilla adicional que impone diferentes tipos de e1
y e2
?
template <typename T, int i>
struct Wrapper : public T{};
struct Z1 {
char c;
[[no_unique_address]] Wrapper<Empty,1> e1;
[[no_unique_address]] Wrapper<Empty,2> e2;
};
Por desgracia, sizeof(Z1)==2
. ¿Hay algún truco para hacer el tamaño de Z1
ser uno?
Estoy probando esto con gcc version 9.2.1
yclang version 9.0.0
En mi solicitud, tengo muchos tipos vacíos del formulario
template <typename T, typename S>
struct Empty{
[[no_unique_address]] T t;
[[no_unique_address]] S s;
};
¡Cuál es un tipo vacío si T
y S
también son tipos vacíos y distintos! Quiero que este tipo esté vacío, incluso si T
y S
son los mismos tipos.
T
sí mismo? Eso generaría distintos tipos. En este momento, el hecho de que ambosWrapper
hereden teT
está frenando ...T
? En este momento,T
es un argumento de plantilla.T
.Respuestas:
No puedes entender eso. Técnicamente hablando, ni siquiera se puede garantizar que va a estar vacío, incluso si
T
yS
son diferentes tipos vacías. Recuerda:no_unique_address
es un atributo; la capacidad de ocultar objetos depende completamente de la implementación. Desde una perspectiva estándar, no puede imponer el tamaño de los objetos vacíos.A medida que las implementaciones de C ++ 20 maduren, debe suponer que
[[no_unique_address]]
generalmente seguirá las reglas de optimización de base vacía. Es decir, siempre que dos objetos del mismo tipo no sean subobjetos, es probable que espere esconderse. Pero en este punto, es una especie de suerte.En cuanto al caso específico
T
yS
ser del mismo tipo, eso simplemente no es posible. A pesar de las implicaciones del nombre "no_unique_address", la realidad es que C ++ requiere que, dados dos punteros a objetos del mismo tipo, esos punteros apunten al mismo objeto o tengan direcciones diferentes. Yo llamo a esto la "regla de identidad única", yno_unique_address
no afecta eso. Desde [intro.object] / 9 :Miembros de tipos vacíos declarados como
[[no_unique_address]]
de tamaño cero, pero tener el mismo tipo lo hace imposible.De hecho, al pensar en ello, intentar ocultar el tipo vacío a través de la anidación todavía viola la regla de identidad única. Considere su caso
Wrapper
yZ1
. Dadoz1
que es una instancia deZ1
, está claro quez1.e1
yz1.e2
son diferentes objetos con diferentes tipos. Sin embargo,z1.e1
no está anidado dentroz1.e2
ni viceversa. Y si bien tienen diferentes tipos,(Empty&)z1.e1
y no(Empty&)z1.e2
son diferentes tipos. Pero sí señalan diferentes objetos.Y por la regla de identidad única, deben tener diferentes direcciones. Así que aunque
e1
ye2
son nominalmente diferentes tipos, sus componentes internos también deben obedecer identidad única contra otros objetos parciales en el mismo objeto que contiene. Recursivamente.Lo que quiere es simplemente imposible en C ++ tal como está actualmente, independientemente de cómo lo intente.
fuente
Por lo que puedo decir, eso no es posible si quieres tener ambos miembros. Pero puede especializarse y tener solo uno de los miembros cuando el tipo es el mismo y está vacío:
Por supuesto, el resto del programa que usa los miembros necesitaría ser cambiado para tratar el caso donde solo hay un miembro. No debería importar qué miembro se usa en este caso; después de todo, es un objeto sin estado sin una dirección única. Las funciones miembro mostradas deberían simplificar eso.
Podría introducir más especializaciones para admitir la compresión recursiva de pares vacíos:
Aún más, comprimir algo así
Empty<Empty<A, char>, A>
.fuente
sizeof(Empty<Empty<A,A>,A>{})==2
cuandoA
es una estructura completamente vacío.get_empty<T>
función. Luego, puede volver a usarlo a laget_empty<T>
izquierda o derecha si ya funciona allí.