se constexpr com static_assert no lambda, qual compilador está correto?

13

Quando queremos usar a static_assertem a if constexpr, devemos tornar a condição dependente de algum parâmetro do modelo. Curiosamente, gcc e clang discordam quando o código é agrupado em uma lambda.

O código a seguir é compilado com o gcc, mas o clang aciona a declaração, mesmo que if constexprnão possa ser verdadeira.

#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>();
}

Exemplo ao vivo aqui .

Pode ser facilmente corrigido substituindo False<T>por False<decltype(x)>.

Então a pergunta é: qual compilador está certo? Eu diria que o gcc está correto porque a condição no static_asserté dependente T, mas não tenho certeza.

Florestan
fonte
Isso faz o mesmo tipo de pergunta, mas vem da direção oposta: stackoverflow.com/questions/59393908/… .
NathanOliver
11
@mfnx Não é possível reproduzir . Você pode compartilhar um exemplo?
NathanOliver 8/01
2
Eu diria que ambos estão certos (NDR mal formado): static_assert(False<int>, "AAA");é equivalente a static_assert(false, "AAA");dentro do lambda.
Jarod42
2
@mfnx Você alterou o valor da constante. Usar o exemplo do OP, onde a constante é f(std::integral_constant<int, 1>{});Wandbox, não aciona a afirmação: wandbox.org/permlink/UFYAmYwtt1ptsndr
NathanOliver
11
@NathanOliver Sim, você está certo, desculpe pelo barulho. Parece que o gcc está correto, pois esse código no constexpr deve ser descartado se a constante> = 0;
mfnx 8/01

Respostas:

1

De [stmt.if] / 2 (ênfase minha)

Se a instrução if for da forma if constexpr, o valor da condição deve ser uma expressão constante convertida contextualmente do tipo bool; esse formulário é chamado de declaração constexpr if. Se o valor da condição convertida for falso, a primeira subestação é uma instrução descartada, caso contrário, a segunda subestação, se presente, é uma instrução descartada. Durante a instanciação de uma entidade de modelo envolvente ([temp.pre]), se a condição não depender de valor após a instanciação, a subestação descartada (se houver) não será instanciada.

Lendo que alguém pensaria que a afirmação estática seria descartada, mas esse não é o caso.

A declaração estática é acionada na primeira fase do modelo, porque o compilador sabe que é sempre falso.

De [temp.res] / 8 (ênfase minha)

A validade de um modelo pode ser verificada antes de qualquer instanciação. [ Observação: saber quais nomes são nomes de tipos permite que a sintaxe de cada modelo seja verificada dessa maneira. - nota final ] O programa está incorreto, não é necessário diagnóstico, se:

  • (8.1) nenhuma especialização válida pode ser gerada para um modelo ou subestação de um constexpr se a instrução dentro de um modelo e o modelo não for instanciada , ou

[...]

Sim, de fato, você False<T>depende T. O problema é que um lambda genérico é ele próprio um modelo e False<T>não depende de nenhum parâmetro de modelo do lambda.

Para um Tque False<T>é falso, a afirmação estática sempre será falsa, independentemente do argumento do modelo enviado para o lambda.

O compilador pode ver que, para qualquer instanciação do modelo operator(), a declaração estática sempre dispara para o T. atual. Portanto, o erro do compilador.

Uma solução para isso seria 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>{});
}

Exemplo ao vivo

Guillaume Racicot
fonte
13

A regra usual aqui é [temp.res] / 8 :

O programa está incorreto, não é necessário diagnóstico, se: nenhuma especialização válida puder ser gerada para um modelo ou uma subestação de um constexpr if, a instrução dentro de um modelo e o modelo não for instanciado

Depois de instanciar foo<T>, o que static_assertvocê tem não é mais dependente. Torna-se static_assert(false)- para todas as instâncias possíveis do operador de chamada do lambda genérico f. Isso é mal formado, não é necessário diagnóstico. Diagnósticos clang, gcc não. Ambos estão corretos.

Observe que não importa que o static_assertaqui seja descartado.

Pode ser facilmente corrigido substituindo False<T>por False<decltype(x)>.

Isso mantém o static_assertdependente dentro do lambda genérico, e agora chegamos a um estado em que hipoteticamente poderia haver uma especialização válida; portanto, não estamos mais mal formados, ndr.

Barry
fonte