si constexpr avec static_assert dans lambda, quel compilateur est correct?

13

Lorsque nous voulons utiliser un static_assertdans un, if constexprnous devons rendre la condition dépendante d'un paramètre de modèle. Fait intéressant, gcc et clang ne sont pas d'accord lorsque le code est enveloppé dans un lambda.

Le code suivant se compile avec gcc, mais clang déclenche l'assertion, même si le if constexprne peut pas être vrai.

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

Exemple en direct ici .

Il peut facilement être corrigé en remplaçant False<T>par False<decltype(x)>.

La question est donc: quel compilateur a raison? Je suppose que gcc est correct car la condition dans le static_assertdépend de T, mais je ne suis pas sûr.

florestan
la source
Cela pose un peu la même question mais venant de la direction opposée: stackoverflow.com/questions/59393908/… .
NathanOliver
1
@mfnx Impossible de se reproduire . Pouvez-vous partager un exemple?
NathanOliver
2
Je dirais que les deux ont raison (NDR mal formé): static_assert(False<int>, "AAA");équivaut à l' static_assert(false, "AAA");intérieur du lambda.
Jarod42
2
@mfnx Vous avez modifié la valeur de la constante. Utiliser l'exemple de l'OP où la constante est f(std::integral_constant<int, 1>{});Wandbox ne déclenche pas l' assertion
NathanOliver
1
@NathanOliver Oui, vous avez raison, désolé pour le bruit. Semble gcc est juste car ce code dans le constexpr doit être rejeté si la constante> = 0;
mfnx

Réponses:

1

Extrait de [stmt.if] / 2 (c'est moi qui souligne)

Si l'instruction if est de la forme if constexpr, la valeur de la condition doit être une expression constante de type bool convertie contextuellement; ce formulaire est appelé une instruction if constexpr. Si la valeur de la condition convertie est fausse, la première sous-instruction est une instruction rejetée, sinon la deuxième sous-instruction, si elle est présente, est une instruction ignorée. Pendant l'instanciation d'une entité de modèle englobante ([temp.pre]), si la condition ne dépend pas de la valeur après son instanciation, la sous-instruction rejetée (le cas échéant) n'est pas instanciée.

Lire que l'on penserait que l'assertion statique serait abandonnée, mais ce n'est pas le cas.

L'assertion statique est déclenchée dans la première phase du modèle car le compilateur sait que c'est toujours faux.

De [temp.res] / 8 (c'est moi qui souligne)

La validité d'un modèle peut être vérifiée avant toute instanciation. [ Remarque: Savoir quels noms sont des noms de type permet de vérifier la syntaxe de chaque modèle de cette manière. - note de fin ] Le programme est mal formé, aucun diagnostic requis, si:

  • (8.1) aucune spécialisation valide ne peut être générée pour un modèle ou une sous-déclaration d'un constexpr si l'instruction dans un modèle et le modèle n'est pas instancié , ou

[...]

Oui en effet, votre False<T>dépend T. Le problème est qu'un lambda générique est lui-même un modèle et False<T>ne dépend d'aucun paramètre de modèle du lambda.

Pour un Tqui False<T>est faux, l'assertion statique sera toujours fausse, quel que soit l'argument modèle envoyé au lambda.

Le compilateur peut voir que pour toute instanciation du modèle operator(), l'assertion statique se déclenchera toujours pour le T. actuel. D'où l'erreur du compilateur.

Une solution pour cela serait de dépendre 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>{});
}

Exemple en direct

Guillaume Racicot
la source
13

La règle habituelle ici est [temp.res] / 8 :

Le programme est mal formé, aucun diagnostic requis, si: aucune spécialisation valide ne peut être générée pour un modèle ou une sous-déclaration d'un constexpr si l'instruction dans un modèle et le modèle n'est pas instancié

Une fois que vous instanciez foo<T>, le que static_assertvous avez n'est plus dépendant. Il devient static_assert(false)- pour toutes les instanciations possibles de l'opérateur d'appel du lambda générique f. C'est mal formé, aucun diagnostic requis. Clang diagnostique, gcc non. Les deux sont corrects.

Notez qu'il n'a pas d'importance que l' static_assertici soit jeté.

Il peut facilement être corrigé en remplaçant False<T>par False<decltype(x)>.

Cela maintient la static_assertdépendance au sein du lambda générique, et maintenant nous entrons dans un état où il pourrait y avoir hypothétiquement une spécialisation valide, donc nous ne sommes plus mal formés, ndr.

Barry
la source