si constexpr con static_assert en lambda, ¿qué compilador es correcto?

13

Cuando queremos usar a static_asserten a if constexprdebemos hacer que la condición dependa de algún parámetro de plantilla. Curiosamente, gcc y clang no están de acuerdo cuando el código está envuelto en una lambda.

El siguiente código se compila con gcc, pero clang activa la afirmación, incluso si if constexprno puede ser cierto.

#include <utility>

template<typename T> constexpr std::false_type False;

template<typename T>
void foo() {

    auto f = [](auto x) {
        constexpr int val = decltype(x)::value;
        if constexpr(val < 0) {
            static_assert(False<T>, "AAA");
        }
    };

    f(std::integral_constant<int, 1>{});
}

int main() {
    foo<int>();
}

Ejemplo en vivo aquí .

Se puede arreglar fácilmente sustituyendo False<T>por False<decltype(x)>.

Entonces la pregunta es: ¿qué compilador es el correcto? Supongo que gcc es correcto porque la condición en el static_assertdepende T, pero no estoy seguro.

Florestan
fuente
Esto hace la misma pregunta pero viene de la dirección opuesta: stackoverflow.com/questions/59393908/… .
NathanOliver
1
@mfnx No se puede reproducir . ¿Puedes compartir un ejemplo?
NathanOliver
2
Yo diría que ambos tienen razón (NDR mal formado): static_assert(False<int>, "AAA");es equivalente al static_assert(false, "AAA");interior de la lambda.
Jarod42
2
@mfnx Has cambiado el valor de la constante. El uso del ejemplo del OP donde la constante es f(std::integral_constant<int, 1>{});Wandbox no activa la afirmación
NathanOliver
1
@NathanOliver Sí, tienes razón, perdón por el ruido. Parece que gcc es correcto ya que ese código en constexpr debe descartarse si la constante> = 0;
mfnx

Respuestas:

1

De [stmt.if] / 2 (énfasis mío)

Si la instrucción if es de la forma if constexpr, el valor de la condición será una expresión constante convertida contextualmente de tipo bool; este formulario se llama constexpr si la declaración. Si el valor de la condición convertida es falso, la primera subestimación es una declaración descartada, de lo contrario, la segunda subestación, si está presente, es una declaración descartada. Durante la creación de instancias de una entidad con plantilla adjunta ([temp.pre]), si la condición no depende del valor después de su creación de instancias, la subestimación descartada (si existe) no se crea una instancia.

Leyendo que uno pensaría que la afirmación estática se descartaría, pero este no es el caso.

La afirmación estática se activa en la primera fase de la plantilla porque el compilador sabe que siempre es falsa.

De [temp.res] / 8 (énfasis mío)

La validez de una plantilla se puede verificar antes de cualquier instanciación. [ Nota: Saber qué nombres son nombres de tipos permite verificar la sintaxis de cada plantilla de esta manera. - nota final ] El programa está mal formado, no se requiere diagnóstico, si:

  • (8.1) no se puede generar una especialización válida para una plantilla o una subtestación de un constexpr si la declaración dentro de una plantilla y la plantilla no se instancia , o

[...]

Sí, de hecho, False<T>depende de usted T. El problema es que un lambda genérico es en sí mismo una plantilla y False<T>no depende de ningún parámetro de plantilla del lambda.

Para un Tque False<T>es falso, la aserción estática siempre será falsa, sin importar qué argumento de plantilla se envíe a la lambda.

El compilador puede ver que para cualquier instanciación de la plantilla operator(), la aserción estática siempre se activará para la T. actual. De ahí el error del compilador.

Una solución para esto sería depender de x:

template<typename T>
void foo() {

    auto f = [](auto x) {
        if constexpr(x < 0) {
            static_assert(False<decltype(x)>, "AAA");
        }
    };

    f(std::integral_constant<int, 1>{});
}

Ejemplo en vivo

Racicot Guillaume
fuente
13

La regla habitual aquí es [temp.res] / 8 :

El programa está mal formado, no se requiere diagnóstico, si: no se puede generar una especialización válida para una plantilla o una declaración de constexpr si la declaración dentro de una plantilla y la plantilla no se instancia

Una vez que crea una instancia foo<T>, lo static_assertque tiene ya no depende. Se convierte static_assert(false), para todas las posibles instancias del operador de llamada de la lambda genérica f. Eso está mal formado, no se requiere diagnóstico. Diagnósticos de argot, gcc no. Ambos son correctos.

Tenga en cuenta que no importa que el static_assertaquí se descarte.

Se puede arreglar fácilmente sustituyendo False<T>por False<decltype(x)>.

Esto mantiene al static_assertdependiente dentro del lambda genérico, y ahora entramos en un estado donde hipotéticamente podría haber una especialización válida, por lo que ya no estamos mal formados, ndr.

Barry
fuente