49
votes

Pourquoi la double négation modifie-t-elle la valeur du concept C ++?

Un de mes amis m'a montré un programme C ++ 20 avec des concepts, qui m'ont perplexe:

struct A { static constexpr bool a = true; };

template <typename T>
concept C = T::a || T::b;

template <typename T>
concept D = !!(T::a || T::b);

static_assert( C<A> );
static_assert( !D<A> );

Il est accepté par tous les compilateurs: https://gcc.godbolt.org/z/e67qkoqce

Ici le concept d code> est le même que le concept c , la seule différence est dans l'opérateur de double négation !! , qui, de la première vue, ne changera pas la valeur du concept. Pour la structure a , le concept c est vrai et le concept d est faux.

Pourriez-vous s'il vous plaît expliquer pourquoi c'est ainsi?


1 commentaires

Pas une réponse parce que je ne suis pas sûr que ce soit correct, mais cela pourrait-il avoir à voir avec le fait que b n'existe pas? Si vous ajoutez un b = true , l'expression se comporte comme prévu.


2 Réponses :


40
votes

Ici, le concept d est le même que le concept c

ils ne le sont pas. Les contraintes (et concept-id) sont normalisées lorsqu'elles sont vérifiées pour la satisfaction et décomposées en contraintes atomiques.

[temp.]

2 La forme normale d'une expression E est une contrainte définie comme suit:

  • La forme normale d'une expression (e) est la forme normale de e .
  • la forme normale d'une expression e1 || E2 est la disjonction des formes normales de E1 et E2.
  • La forme normale d'une expression e1 && e2 est la conjonction des formulaires normaux de e1 et e2 .
  • La forme normale d'un concept-id c est la forme normale de l'expression de contrainte de c , après substituant a1 , a2 , ..., an pour les paramètres de modèle respectifs de c dans le mappages de paramètres dans chaque contrainte atomique. Si une telle substitution entraîne un type ou une expression non valide, le programme est mal formé; Aucun diagnostic n'est requis.
  • La forme normale de toute autre expression e est la contrainte atomique dont l'expression est e et dont le mappage des paramètres est l'identité cartographie.

pour c Les contraintes atomiques sont t :: a et t :: b .
Pour d , il n'y a qu'une seule contrainte atomique qui est !! (t :: a || t :: b) .

La défaillance de la substitution dans une contrainte atomique ne le rend pas satisfait et évalue à false . c est une disjonction d'une contrainte qui est satisfait, et celle qui ne l'est pas, donc c'est true . d est faux car sa contrainte atomique unique a une défaillance de substitution.


0 commentaires

26
votes

La chose importante à réaliser est que par [temp.constr.constront ] , les contraintes atomiques sont composées uniquement via des conjonctions (via un niveau supérieur && ) et des disjonctions (via le niveau supérieur || ). La négation doit être considérée comme faisant partie d'une contrainte, et non de la négation d'une contrainte. Il y a même un note non normative pointant ceci explicitement.

Dans cet esprit, nous pouvons examiner les deux cas. c est une disjonction de deux contraintes atomiques: t :: a et t :: b . per / 3 , disjonctions Utilisez des comportements de court-circuit lors de la vérification de la satisfaction. Cela signifie que t :: a est vérifié en premier. Puisqu'il réussit, toute la contrainte c est satisfaite sans jamais vérifier la seconde.

d , d'autre part, est une contrainte atomique: !! (t :: a || t :: b) . Le || ne crée aucune disjonction de quelque manière que ce soit, cela fait simplement partie de l'expression. Nous nous tournons vers [temp.constr.atomic ] / 3 Pour voir que les paramètres de modèle sont substitués. Ce paragraphe indique également que si la substitution échoue, la contrainte n'est pas satisfaite. Comme le suggère la note précédente, les négations à l'avant ne sont même pas encore considérées. En fait, avoir une seule négation donne le même résultat.


Maintenant, la question évidente est de savoir pourquoi les concepts ont été conçus de cette façon. Malheureusement, je ne me souviens pas avoir rencontré un raisonnement pour cela dans les discussions de conférence du designer et autres communications. Le meilleur que j'ai pu trouver était ce bit de la proposition originale:

Bien que la négation se soit avérée assez courante dans nos contraintes (voir section 5.3), nous n'avons pas jugé nécessaire d'attribuer une sémantique plus profonde à l'opérateur.

À mon avis, cela sous-vende probablement vraiment la pensée qui a été mise dans la décision. J'adorerais voir le designer élaborer à ce sujet, car je suis convaincu qu'il a plus à dire que cette petite citation.


0 commentaires