¿Por qué introducir `std::launder` en lugar de que el compilador se encargue de ello?

6 minutos de lectura

ein es compatible con el avatar de usuario de Moderator Strike
ein apoya la huelga de moderadores

Acabo de leer

¿Cuál es el propósito de std::launder?

y francamente, me quedo rascándome la cabeza.

Comencemos con el segundo ejemplo en la respuesta aceptada de @NicolBolas:

aligned_storage<sizeof(int), alignof(int)>::type data; 
new(&data) int; 
int *p = std::launder(reinterpret_cast<int*>(&data)); 

[basic.life]/8 nos dice que, si asigna un nuevo objeto en el almacenamiento del antiguo, no puede acceder al nuevo objeto a través de punteros al antiguo. std::launder nos permite esquivar eso.

Entonces, ¿por qué no simplemente cambiar el estándar de idioma para que acceder data a través de un reinterpret_cast<int*>(&data) es válido/apropiado? En la vida real, el lavado de dinero es una forma de ocultar la realidad a la ley. Pero no tenemos nada que ocultar, aquí estamos haciendo algo perfectamente legítimo. Entonces, ¿por qué el compilador no puede simplemente cambiar su comportamiento a su std::launder() comportamiento cuando nota que estamos accediendo data ¿Por aquí?

En el primer ejemplo:

X *p = new (&u.x) X {2};

Debido a que X es trivial, no necesitamos destruir el objeto anterior antes de crear uno nuevo en su lugar, por lo que este es un código perfectamente legal. El nuevo objeto tendrá su miembro n 2.

Así que dime… ¿qué será u.x.n ¿devolver?

La respuesta obvia será 2. Pero eso es incorrecto, porque el compilador puede asumir que un verdadero const variable (no simplemente una const&, sino una variable de objeto declarada const) nunca cambiará. Pero lo acabamos de cambiar.

Entonces, ¿por qué no hacer que el compilador no ¿Se permitirá hacer la suposición cuando escribimos este tipo de código, accediendo al campo constante a través del puntero?

¿Por qué es razonable tener esta pseudofunción para hacer un agujero en la semántica del lenguaje formal, en lugar de establecer la semántica como debe ser dependiendo de si el código hace o no algo como en estos ejemplos?

  • supongo que launder optimizaciones deshabilitadas, en un alcance muy pequeño.

    – mojar

    12 de febrero de 2021 a las 18:08

  • Entonces, ¿por qué no hacer que el compilador no pueda hacer suposiciones cuando escribimos este tipo de código?“¿Quieres decir, además del hecho obvio de que el compilador tendría que generar código subóptimo el 99% del tiempo? La mayoría de la gente no andar recreando objetos almacenados, así que asumiendo que u.x.n no cambia es completamente razonable.

    – Nicolás Bolas

    12 de febrero de 2021 a las 18:17


  • @dyp: ¿Adivina qué? Tiene que asumir que ya.

    – ein apoya la huelga de moderadores

    12 de febrero de 2021 a las 19:25


  • @einpoklum: “Tiene que asumir eso ya.” Estás confundiendo el código compilado con el estándar requiere suceder No hay nada en el estándar que requiera explícitamente que los compiladores asuman algo por el estilo.

    – Nicolás Bolas

    12 de febrero de 2021 a las 19:38

  • @NicolBolas: ¿Está diciendo que ese ejemplo es solo una optimización perdida? Tenía la impresión de que un const El calificador en un valor para una función no es una garantía para el compilador, sino una precaución de seguridad para permitir que el compilador evite que usted, el autor, lo cambie.

    – ein apoya la huelga de moderadores

    12 de febrero de 2021 a las 19:55

Avatar de usuario de Nicol Bolas
Nicolás Bolas

dependiendo de si el código hace o no algo como en estos ejemplos

Debido a que el compilador no siempre puede saber cuándo data se está accediendo “de esta manera”.

Tal como están las cosas actualmente, el compilador puede asumir que, para el siguiente código:

struct foo{ int const x; };

void some_func(foo*);

int bar() {
    foo f { 123 };
    some_func(&f);
    return f.x;
}

bar siempre devolverá 123. El compilador puede generar código que realmente acceda al objeto. Pero el modelo de objetos no requerir este. f.x es un const objeto (no una referencia/puntero a const), y por lo tanto no se puede cambiar. Y f se requiere nombrar siempre el mismo objeto (de hecho, estas son las partes del estándar que tendría que cambiar). Por lo tanto, el valor de f.x no se puede cambiar por ningún medio que no sea UB.

¿Por qué es razonable tener esta pseudofunción para abrir un agujero en la semántica del lenguaje formal?

Esto era realmente discutido. Ese documento menciona cuánto tiempo han existido estos problemas (es decir, desde C++ 03) y, a menudo, se han empleado las optimizaciones posibles gracias a este modelo de objetos.

La propuesta fue rechazada con el argumento de que en realidad no solucionaría el problema. De este informe de viaje:

Sin embargo, durante la discusión salió a la luz que la alternativa propuesta no manejaría todos los escenarios afectados (particularmente los escenarios donde los punteros de vtable están en juego), y no obtuvo consenso.

El informe no entra en ningún detalle en particular sobre el asunto, y las discusiones en cuestión no están disponibles públicamente. Pero la propia propuesta sí señala que no permitiría desvirtualizar un segundo llamada de función virtual, ya que la primera llamada puede haber creado un nuevo objeto. Entonces, incluso P0532 no haría launder innecesario, simplemente menos necesario.

  • Pero, ¿y si por dentro some_func()Yo suelo const_cast sobre el x ¿campo?

    – ein apoya la huelga de moderadores

    12 de febrero de 2021 a las 20:27


  • re. su segundo comentario, los compiladores que carecen de una optimización no son motivos para sospechar del estándar. Otras razones para las optimizaciones perdidas incluyen: nadie se puso a trabajar para agregar este caso al optimizador; y la base de usuarios que espera un comportamiento no estándar. Caso similar sin la estructura se optimiza; estructuras con const los miembros son poco comunes

    –MM

    12 de febrero de 2021 a las 21:19


  • @einpoklum modificando f.x durante la vida de x (a través de const_cast) es UB por [dcl.type.cv]/4. Poner fin a la vida de x y/o f dentro some_func se permitiría por sí solo, pero eso causaría el acceso posterior secuenciado en bar ser UB por [basic.life]/(8.3).

    – Aschepler

    12 de febrero de 2021 a las 21:20

  • Me parece bien. Pero dada esta situación, yo diría std::launder es lo contrario de aptly-named. std::soil sería mucho más apropiado.

    – ein apoya la huelga de moderadores

    12 de febrero de 2021 a las 22:50

  • Creo que el ejemplo de “subobjeto const” ya no es relevante: github.com/cplusplus/draft/commit/…

    – mojar

    15 de febrero de 2021 a las 8:46

¿Ha sido útil esta solución?