Nueva expresión con constructor consteval en contexto constexpr

8 minutos de lectura

avatar de usuario de user17732522
usuario17732522

struct A {       
    consteval A() {};
};

constexpr bool g() {
    auto a = new A;
    delete a;
    return true;
}

int main() {
    static_assert(g());
}

https://godbolt.org/z/jsq35WxKs

GCC y MSVC rechazan el programa, ICC y Clang lo aceptan:

///MSVC: 
<source>(6): error C7595: 'A::A': call to immediate function is not a constant expression
Compiler returned: 2

//GCC:
<source>: In function 'constexpr bool g()':
<source>:6:18: error: the value of '<anonymous>' is not usable in a constant expression
    6 |     auto a = new A;
      |                  ^
<source>:6:18: note: '<anonymous>' was not declared 'constexpr'
<source>:7:12: error: type '<type error>' argument given to 'delete', expected pointer
    7 |     delete a;
      |            ^
Compiler returned: 1

Aunque reemplazando new A por new A() da como resultado que GCC acepte el programa también (pero no para new A{} o).


Al realizar al menos uno de los siguientes cambios, los cuatro compiladores aceptan el programa:

  1. Reemplazar consteval con constexpr

  2. Reemplazar constexpr con consteval

  3. Reemplazar

    auto a = new A;
    delete a;
    

    con

    auto alloc = std::allocator<A>{};
    auto a = alloc.allocate(1);
    std::construct_at(a);
    std::destroy_at(a);
    alloc.deallocate(a, 1);
    

    con A a;con auto&& a = A{}; o con A{};

Solo excepciones:

  • Clang trunk con libstdc++ parece fallar en la compilación con el std::allocator versión aparentemente debido a un error no relacionado. Con Clang 13 o libc++ también se acepta.

    In file included from <source>:1:
    In file included from [...]/memory:78:
    [...]/shared_ptr_atomic.h:459:14: error: missing 'typename' prior to dependent type name '_Atomic_count::pointer'
      static _Atomic_count::pointer
    
  • MSVC rechaza la std::allocator versión siempre que haya consteval en el constructor:

    error C7595: 'A::A': call to immediate function is not a constant expression
    <source>(10): note: see reference to function template instantiation '_Ty *std::construct_at<_Ty,,void>(_Ty *const ) noexcept(false)' being compiled
            with
            [
                _Ty=A
            ]
    

reemplazando static_assert(g()); con g() o eliminar la llamada por completo no parece tener ningún impacto en estos resultados.


¿Qué compiladores son correctos y si el original está mal formado, por qué solo esa combinación particular de calificadores y método de construcción no está permitida?


Motivado por los comentarios bajo esta respuesta.

  • La versión del asignador en realidad no inicializa el objeto. Calculo una llamada para construct se comportará como la nueva expresión desnuda.

    – StoryTeller – Unslander Mónica

    17 ene a las 19:41

  • Interesantemente cambiando new A a new A() hace feliz a GCC con el código.

    – Il Capitán

    17 ene a las 22:36

  • @SolomonUcko Parece que ese error se solucionó hace algunas versiones: godbolt.org/z/qcxhvefxv

    – usuario17732522

    21 de enero a las 17:28

  • No leo los periódicos estándar para verificar mis pensamientos. Pero según tengo entendido: consteval DEBE usarse solo en contextos de tiempo de compilación. Dado que constexpr se puede usar en tiempo de compilación Y en tiempo de ejecución, rechazará las expresiones consteval. Curiosamente, cambié la función g así: constexpr bool g() { if constexpr( std::is_constant_evaluated() ) { auto a = new A; eliminar un; } devuelve verdadero; } pero el código aún se rechaza en MSVC 17.3.5 (C++ más reciente).

    – Soluciones TeaAge

    5 oct a las 8:34

  • @TeaAgeSolutions No, una llamada de función a un consteval La función puede aparecer en cualquier lugar (explícita o implícitamente), pero independientemente del contexto, la llamada debe formar por sí misma una expresión constante, asumiendo que no aparece dentro de otra consteval función. La pregunta aquí es qué significa que la llamada implícita al constructor forme una expresión constante y cómo interactúa con la new-semántica de la expresión. Volviendo a esta pregunta, creo que el estándar no especifica esto correctamente, de manera similar a cómo no especifica el comportamiento de constexpr variables correctamente.

    – usuario17732522

    24 oct a las 14:54

La redacción pertinente es [expr.const]/13:

Una expresión o conversión es una invocación inmediata si es una invocación explícita o implícita potencialmente evaluada de una función inmediata y no está en un contexto de función inmediata. Una invocación inmediata será una expresión constante.

Tenga en cuenta las palabras ‘o conversión’ y ‘invocación implícita’ – esto parece implicar que la regla está destinada a aplicarse por llamada de función.1 La evaluación de una sola expresión atómica puede consistir en múltiples llamadas de este tipo, como en el caso de, por ejemplo, el nueva expresión que puede llamar a una función de asignación, un constructor y una función de desasignación. Si el constructor seleccionado es constevalla parte de la evaluación de la nueva expresión que inicializa el objeto (es decir, la llamada al constructor), y solamente esa parte, es una invocación inmediata. Bajo esta interpretación, usando new con un consteval El constructor no debe estar mal formado independientemente del contexto, incluso fuera de una expresión constante, siempre que la inicialización del objeto sea constante, por supuesto.

Sin embargo, hay un problema con esta lectura: la última oración dice claramente que una invocación inmediata debe ser una expresión. Una ‘llamada subatómica’ como se describe arriba no es una, no tiene una categoría de valor y posiblemente no podría satisfacer la definición de una expresión constante ([expr.const]/11):

A expresión constante es una expresión constante central glvalue que se refiere a una entidad que es un resultado permitido de una expresión constante (como se define a continuación), o una expresión constante central prvalue cuyo valor satisface las siguientes restricciones […]

Una interpretación literal de esta redacción excluiría ningún uso de un consteval constructor fuera del contexto de una función inmediata, ya que una llamada nunca puede aparecer como una expresión independiente. Claramente, este no es el significado previsto; entre otras cosas, dejaría inutilizables partes de la biblioteca estándar.

Una versión más optimista (pero también menos fiel a las palabras tal como están escritas) de esta lectura es que la expresión atómica que contiene la llamada (formalmente: la expresión que la llamada es una subexpresión inmediata de 2) debe ser una expresión constante. Esto todavía no permite que su new A construct porque no es una expresión constante por sí misma, y ​​también deja cierta incertidumbre en casos como la inicialización de parámetros de funciones o variables en general.


Me inclino a creer que la primera lectura es la prevista, y que new A debería estar bien, pero claramente hay una divergencia en la implementación.

En cuanto al requisito contradictorio ‘deberá ser una expresión constante’, este no es el único lugar en el estándar donde aparece así. Anteriormente en la misma sección, [expr.const]/2.2:

Una variable o un objeto temporal o se inicializa constantemente si […]

  • la expresión completa de su inicialización es una expresión constante cuando se interpreta como una expresión-constante […]

Claramente, se supone que lo siguiente es válido:

constexpr A a;

Pero no hay una expresión constante a la vista.


Entonces, para responder a tu pregunta:

Si la llamada a g se está evaluando como parte de una expresión evaluada manifiestamente constante no importa3 independientemente de la interpretación de [expr.const]/13 vas con. new A está bien formado incluso durante la evaluación normal o está mal formado en cualquier lugar fuera del contexto de una función inmediata.

Aparentemente, Clang e ICC implementan el primer conjunto de reglas, mientras que GCC y MSVC se adhieren al último. Con la excepción de que GCC acepte new A() como un valor atípico (que es claramente un insecto), ninguno está equivocado, la redacción es simplemente defectuosa.


[1] CWG2410 corrige la redacción para incluir correctamente cosas como llamadas al constructor (que no son expresiones ni conversiones).

[2] Sí, una no expresión puede ser una subexpresión.

[3] Tal requisito sería imposible de hacer cumplir.

  • lo cual es claramente un error“: Supongo que quiere decir aquí que debe ser un error porque el compilador está siendo inconsistente consigo mismo, no porque new A() está definitivamente mal formado, ¿verdad?

    – usuario17732522

    hace 2 días

  • Como mencioné en los comentarios debajo de la pregunta cuando lo revisé, llegué a una conclusión similar a la suya. Así que esto tiene algún sentido para mí. Sin embargo, realmente no veo por qué GCC considera una ubicación nueva a través de std::construct_at diferente de la construcción del objeto en la nueva expresión de asignación. Esa parte especialmente me parece inconsistente.

    – usuario17732522

    hace 2 días


  • si, me referia a () haciendo una diferencia en absoluto en este caso. std::construct_at ser aceptado es una manifestación del mismo error: llamarlo sin argumentos es equivalente a una ubicación nueva que se ve así: new(...) A(). Agregar un maniquí int parámetro hace que sea rechazado también.

    – pato

    hace 2 días

  • Ah bien. GCC podría parecer estar haciendo algo diferente para la inicialización de valores específicamente.

    – usuario17732522

    hace 2 días

¿Ha sido útil esta solución?