[clang] [Clang][C++26] Implement "Ordering of constraints involving fold expr… (PR #98160)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 9 07:10:39 PDT 2024
https://github.com/cor3ntin created https://github.com/llvm/llvm-project/pull/98160
…essions"
Implement https://isocpp.org/files/papers/P2963R3.pdf
>From ba2b6ab4fd0aa5a79a849d71f2857256f8fc1c73 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 9 Jul 2024 16:08:44 +0200
Subject: [PATCH] [Clang][C++26] Implement "Ordering of constraints involving
fold expressions"
Implement https://isocpp.org/files/papers/P2963R3.pdf
---
clang/docs/ReleaseNotes.rst | 3 +
clang/include/clang/Sema/Sema.h | 5 +
clang/include/clang/Sema/SemaConcept.h | 142 +++++-
clang/lib/Sema/SemaConcept.cpp | 604 ++++++++++++++++--------
clang/lib/Sema/SemaTemplateVariadic.cpp | 4 +
clang/test/SemaCXX/cxx2c-fold-exprs.cpp | 239 ++++++++++
clang/www/cxx_status.html | 2 +-
7 files changed, 781 insertions(+), 218 deletions(-)
create mode 100644 clang/test/SemaCXX/cxx2c-fold-exprs.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 838cb69f647ee..7d0b2115f8e53 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -271,6 +271,9 @@ C++2c Feature Support
- Implemented `P3144R2 Deleting a Pointer to an Incomplete Type Should be Ill-formed <https://wg21.link/P3144R2>`_.
+- Implemented `P2963R3 Ordering of constraints involving fold expressions <https://wg21.link/P2963R3>`_.
+
+
Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Substitute template parameter pack, when it is not explicitly specified
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 75a80540dbcbf..4a07da6b7bfd9 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14062,6 +14062,11 @@ class Sema final : public SemaBase {
const DeclarationNameInfo &NameInfo,
SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
+ /// Collect the set of unexpanded parameter packs within the given
+ /// expression.
+ static void collectUnexpandedParameterPacks(
+ Expr *E, SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
+
/// Invoked when parsing a template argument followed by an
/// ellipsis, which creates a pack expansion.
///
diff --git a/clang/include/clang/Sema/SemaConcept.h b/clang/include/clang/Sema/SemaConcept.h
index 711443505174f..b6327e729768b 100644
--- a/clang/include/clang/Sema/SemaConcept.h
+++ b/clang/include/clang/Sema/SemaConcept.h
@@ -75,6 +75,17 @@ struct AtomicConstraint {
}
};
+struct FoldExpandedConstraint;
+
+using NormalFormConstraint =
+ llvm::PointerUnion<AtomicConstraint *, FoldExpandedConstraint *>;
+struct NormalizedConstraint;
+using NormalForm =
+ llvm::SmallVector<llvm::SmallVector<NormalFormConstraint, 2>, 4>;
+
+NormalForm makeCNF(const NormalizedConstraint &Normalized);
+NormalForm makeDNF(const NormalizedConstraint &Normalized);
+
/// \brief A normalized constraint, as defined in C++ [temp.constr.normal], is
/// either an atomic constraint, a conjunction of normalized constraints or a
/// disjunction of normalized constraints.
@@ -87,26 +98,17 @@ struct NormalizedConstraint {
std::pair<NormalizedConstraint, NormalizedConstraint> *, 1,
CompoundConstraintKind>;
- llvm::PointerUnion<AtomicConstraint *, CompoundConstraint> Constraint;
+ llvm::PointerUnion<AtomicConstraint *, FoldExpandedConstraint *,
+ CompoundConstraint>
+ Constraint;
NormalizedConstraint(AtomicConstraint *C): Constraint{C} { };
+ NormalizedConstraint(FoldExpandedConstraint *C) : Constraint{C} {};
+
NormalizedConstraint(ASTContext &C, NormalizedConstraint LHS,
- NormalizedConstraint RHS, CompoundConstraintKind Kind)
- : Constraint{CompoundConstraint{
- new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
- std::move(LHS), std::move(RHS)}, Kind}} { };
-
- NormalizedConstraint(ASTContext &C, const NormalizedConstraint &Other) {
- if (Other.isAtomic()) {
- Constraint = new (C) AtomicConstraint(*Other.getAtomicConstraint());
- } else {
- Constraint = CompoundConstraint(
- new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
- NormalizedConstraint(C, Other.getLHS()),
- NormalizedConstraint(C, Other.getRHS())},
- Other.getCompoundKind());
- }
- }
+ NormalizedConstraint RHS, CompoundConstraintKind Kind);
+
+ NormalizedConstraint(ASTContext &C, const NormalizedConstraint &Other);
NormalizedConstraint(NormalizedConstraint &&Other):
Constraint(Other.Constraint) {
Other.Constraint = nullptr;
@@ -126,6 +128,9 @@ struct NormalizedConstraint {
}
bool isAtomic() const { return Constraint.is<AtomicConstraint *>(); }
+ bool isFoldExpanded() const {
+ return Constraint.is<FoldExpandedConstraint *>();
+ }
NormalizedConstraint &getLHS() const {
assert(!isAtomic() && "getLHS called on atomic constraint.");
@@ -143,6 +148,12 @@ struct NormalizedConstraint {
return Constraint.get<AtomicConstraint *>();
}
+ FoldExpandedConstraint *getFoldExpandedConstraint() const {
+ assert(isFoldExpanded() &&
+ "getFoldExpandedConstraint called on non-fold-expanded constraint.");
+ return Constraint.get<FoldExpandedConstraint *>();
+ }
+
private:
static std::optional<NormalizedConstraint>
fromConstraintExprs(Sema &S, NamedDecl *D, ArrayRef<const Expr *> E);
@@ -150,6 +161,103 @@ struct NormalizedConstraint {
fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E);
};
+struct FoldExpandedConstraint {
+ enum class FoldOperatorKind { FoAnd, FoOr } Kind;
+ NormalizedConstraint Constraint;
+ const Expr *Pattern;
+
+ FoldExpandedConstraint(FoldOperatorKind K, NormalizedConstraint C,
+ const Expr *Pattern)
+ : Kind(K), Constraint(std::move(C)), Pattern(Pattern) {};
+
+ template <typename AtomicSubsumptionEvaluator>
+ bool subsumes(const FoldExpandedConstraint &Other,
+ AtomicSubsumptionEvaluator E) const;
+
+ static bool AreSubsumptionElligible(const FoldExpandedConstraint &A,
+ const FoldExpandedConstraint &B);
+};
+
+const NormalizedConstraint *getNormalizedAssociatedConstraints(
+ Sema &S, NamedDecl *ConstrainedDecl,
+ ArrayRef<const Expr *> AssociatedConstraints);
+
+template <typename AtomicSubsumptionEvaluator>
+bool subsumes(const NormalForm &PDNF, const NormalForm &QCNF,
+ AtomicSubsumptionEvaluator E) {
+ // C++ [temp.constr.order] p2
+ // Then, P subsumes Q if and only if, for every disjunctive clause Pi in the
+ // disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in
+ // the conjuctive normal form of Q, where [...]
+ for (const auto &Pi : PDNF) {
+ for (const auto &Qj : QCNF) {
+ // C++ [temp.constr.order] p2
+ // - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if
+ // and only if there exists an atomic constraint Pia in Pi for which
+ // there exists an atomic constraint, Qjb, in Qj such that Pia
+ // subsumes Qjb.
+ bool Found = false;
+ for (NormalFormConstraint Pia : Pi) {
+ for (NormalFormConstraint Qjb : Qj) {
+ if (Pia.is<FoldExpandedConstraint *>() &&
+ Qjb.is<FoldExpandedConstraint *>()) {
+ if (Pia.get<FoldExpandedConstraint *>()->subsumes(
+ *Qjb.get<FoldExpandedConstraint *>(), E)) {
+ Found = true;
+ break;
+ }
+ } else if (Pia.is<AtomicConstraint *>() &&
+ Qjb.is<AtomicConstraint *>()) {
+ if (E(*Pia.get<AtomicConstraint *>(),
+ *Qjb.get<AtomicConstraint *>())) {
+ Found = true;
+ break;
+ }
+ }
+ }
+ if (Found)
+ break;
+ }
+ if (!Found)
+ return false;
+ }
+ }
+ return true;
+}
+
+template <typename AtomicSubsumptionEvaluator>
+bool subsumes(Sema &S, NamedDecl *DP, ArrayRef<const Expr *> P, NamedDecl *DQ,
+ ArrayRef<const Expr *> Q, bool &Subsumes,
+ AtomicSubsumptionEvaluator E) {
+ // C++ [temp.constr.order] p2
+ // In order to determine if a constraint P subsumes a constraint Q, P is
+ // transformed into disjunctive normal form, and Q is transformed into
+ // conjunctive normal form. [...]
+ auto *PNormalized = getNormalizedAssociatedConstraints(S, DP, P);
+ if (!PNormalized)
+ return true;
+ const NormalForm PDNF = makeDNF(*PNormalized);
+
+ auto *QNormalized = getNormalizedAssociatedConstraints(S, DQ, Q);
+ if (!QNormalized)
+ return true;
+ const NormalForm QCNF = makeCNF(*QNormalized);
+
+ Subsumes = subsumes(PDNF, QCNF, E);
+ return false;
+}
+
+template <typename AtomicSubsumptionEvaluator>
+bool FoldExpandedConstraint::subsumes(const FoldExpandedConstraint &Other,
+ AtomicSubsumptionEvaluator E) const {
+ if (Kind != Other.Kind || !AreSubsumptionElligible(*this, Other))
+ return false;
+
+ const NormalForm PDNF = makeDNF(this->Constraint);
+ const NormalForm QCNF = makeCNF(Other.Constraint);
+ return clang::subsumes(PDNF, QCNF, E);
+}
+
} // clang
#endif // LLVM_CLANG_SEMA_SEMACONCEPT_H
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 202dd86c67f62..b86e2d51e380b 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -65,6 +65,7 @@ class LogicalBinOp {
const Expr *getLHS() const { return LHS; }
const Expr *getRHS() const { return RHS; }
+ OverloadedOperatorKind getOp() const { return Op; }
ExprResult recreateBinOp(Sema &SemaRef, ExprResult LHS) const {
return recreateBinOp(SemaRef, LHS, const_cast<Expr *>(getRHS()));
@@ -177,65 +178,160 @@ struct SatisfactionStackRAII {
};
} // namespace
-template <typename AtomicEvaluator>
+template <typename ConstraintEvaluator>
static ExprResult
calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
ConstraintSatisfaction &Satisfaction,
- AtomicEvaluator &&Evaluator) {
- ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
+ ConstraintEvaluator &&Evaluator);
- if (LogicalBinOp BO = ConstraintExpr) {
- size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
- ExprResult LHSRes = calculateConstraintSatisfaction(
- S, BO.getLHS(), Satisfaction, Evaluator);
+template <typename ConstraintEvaluator>
+static ExprResult calculateConstraintSatisfaction(
+ Sema &S, const Expr *LHS, OverloadedOperatorKind Op, const Expr *RHS,
+ ConstraintSatisfaction &Satisfaction, ConstraintEvaluator &Evaluator) {
+ size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
- if (LHSRes.isInvalid())
- return ExprError();
+ ExprResult LHSRes =
+ calculateConstraintSatisfaction(S, LHS, Satisfaction, Evaluator);
- bool IsLHSSatisfied = Satisfaction.IsSatisfied;
+ if (LHSRes.isInvalid())
+ return ExprError();
- if (BO.isOr() && IsLHSSatisfied)
- // [temp.constr.op] p3
- // A disjunction is a constraint taking two operands. To determine if
- // a disjunction is satisfied, the satisfaction of the first operand
- // is checked. If that is satisfied, the disjunction is satisfied.
- // Otherwise, the disjunction is satisfied if and only if the second
- // operand is satisfied.
- // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
- return LHSRes;
-
- if (BO.isAnd() && !IsLHSSatisfied)
- // [temp.constr.op] p2
- // A conjunction is a constraint taking two operands. To determine if
- // a conjunction is satisfied, the satisfaction of the first operand
- // is checked. If that is not satisfied, the conjunction is not
- // satisfied. Otherwise, the conjunction is satisfied if and only if
- // the second operand is satisfied.
- // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
- return LHSRes;
-
- ExprResult RHSRes = calculateConstraintSatisfaction(
- S, BO.getRHS(), Satisfaction, std::forward<AtomicEvaluator>(Evaluator));
- if (RHSRes.isInvalid())
- return ExprError();
+ bool IsLHSSatisfied = Satisfaction.IsSatisfied;
+
+ if (Op == clang::OO_PipePipe && IsLHSSatisfied)
+ // [temp.constr.op] p3
+ // A disjunction is a constraint taking two operands. To determine if
+ // a disjunction is satisfied, the satisfaction of the first operand
+ // is checked. If that is satisfied, the disjunction is satisfied.
+ // Otherwise, the disjunction is satisfied if and only if the second
+ // operand is satisfied.
+ // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
+ return LHSRes;
+
+ if (Op == clang::OO_AmpAmp && !IsLHSSatisfied)
+ // [temp.constr.op] p2
+ // A conjunction is a constraint taking two operands. To determine if
+ // a conjunction is satisfied, the satisfaction of the first operand
+ // is checked. If that is not satisfied, the conjunction is not
+ // satisfied. Otherwise, the conjunction is satisfied if and only if
+ // the second operand is satisfied.
+ // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
+ return LHSRes;
+
+ ExprResult RHSRes = calculateConstraintSatisfaction(
+ S, RHS, Satisfaction, std::forward<ConstraintEvaluator>(Evaluator));
+ if (RHSRes.isInvalid())
+ return ExprError();
+
+ bool IsRHSSatisfied = Satisfaction.IsSatisfied;
+ // Current implementation adds diagnostic information about the falsity
+ // of each false atomic constraint expression when it evaluates them.
+ // When the evaluation results to `false || true`, the information
+ // generated during the evaluation of left-hand side is meaningless
+ // because the whole expression evaluates to true.
+ // The following code removes the irrelevant diagnostic information.
+ // FIXME: We should probably delay the addition of diagnostic information
+ // until we know the entire expression is false.
+ if (Op == clang::OO_PipePipe && IsRHSSatisfied) {
+ auto EffectiveDetailEnd = Satisfaction.Details.begin();
+ std::advance(EffectiveDetailEnd, EffectiveDetailEndIndex);
+ Satisfaction.Details.erase(EffectiveDetailEnd, Satisfaction.Details.end());
+ }
+ if (!LHSRes.isUsable() || !RHSRes.isUsable())
+ return ExprEmpty();
+
+ return BinaryOperator::Create(S.Context, LHSRes.get(), RHSRes.get(),
+ BinaryOperator::getOverloadedOpcode(Op),
+ S.Context.BoolTy, VK_PRValue, OK_Ordinary,
+ LHS->getBeginLoc(), FPOptionsOverride{});
+}
+
+template <typename ConstraintEvaluator>
+static ExprResult
+calculateConstraintSatisfaction(Sema &S, const CXXFoldExpr *FE,
+ ConstraintSatisfaction &Satisfaction,
+ ConstraintEvaluator &&Evaluator) {
+ bool Conjunction = FE->getOperator() == BinaryOperatorKind::BO_LAnd;
+ size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
+
+ ExprResult Out;
+ if (FE->isLeftFold() && FE->getInit()) {
+ Out = calculateConstraintSatisfaction(S, FE->getInit(), Satisfaction,
+ Evaluator);
+ if (Out.isInvalid())
+ return ExprError();
+ bool IsLHSSatisfied = Satisfaction.IsSatisfied;
+ if (Conjunction && !IsLHSSatisfied) {
+ return Out;
+ }
+ if (!Conjunction && IsLHSSatisfied) {
+ return Out;
+ }
+ }
+ std::optional<unsigned> NumExpansions =
+ Evaluator.EvaluateFoldExpandedConstraintSize(FE);
+ if (!NumExpansions)
+ return ExprError();
+ for (unsigned I = 0; I < *NumExpansions; I++) {
+ Sema::ArgumentPackSubstitutionIndexRAII SubstIndex(S, I);
+ ExprResult Res = calculateConstraintSatisfaction(S, FE->getPattern(),
+ Satisfaction, Evaluator);
+ if (Res.isInvalid())
+ return ExprError();
bool IsRHSSatisfied = Satisfaction.IsSatisfied;
- // Current implementation adds diagnostic information about the falsity
- // of each false atomic constraint expression when it evaluates them.
- // When the evaluation results to `false || true`, the information
- // generated during the evaluation of left-hand side is meaningless
- // because the whole expression evaluates to true.
- // The following code removes the irrelevant diagnostic information.
- // FIXME: We should probably delay the addition of diagnostic information
- // until we know the entire expression is false.
- if (BO.isOr() && IsRHSSatisfied) {
+ if (!Conjunction && IsRHSSatisfied) {
auto EffectiveDetailEnd = Satisfaction.Details.begin();
std::advance(EffectiveDetailEnd, EffectiveDetailEndIndex);
Satisfaction.Details.erase(EffectiveDetailEnd,
Satisfaction.Details.end());
}
+ if (Out.isUnset())
+ Out = Res;
+ else if (!Res.isUnset()) {
+ Out = BinaryOperator::Create(
+ S.Context, Out.get(), Res.get(), FE->getOperator(), S.Context.BoolTy,
+ VK_PRValue, OK_Ordinary, FE->getBeginLoc(), FPOptionsOverride{});
+ }
+ if (Conjunction && !IsRHSSatisfied)
+ return Out;
+ if (!Conjunction && IsRHSSatisfied)
+ return Out;
+ }
+
+ if (FE->isRightFold() && FE->getInit()) {
+ ExprResult Res = calculateConstraintSatisfaction(S, FE->getInit(),
+ Satisfaction, Evaluator);
+ if (Out.isInvalid())
+ return ExprError();
+
+ if (Out.isUnset())
+ Out = Res;
+ else if (!Res.isUnset()) {
+ Out = BinaryOperator::Create(
+ S.Context, Out.get(), Res.get(), FE->getOperator(), S.Context.BoolTy,
+ VK_PRValue, OK_Ordinary, FE->getBeginLoc(), FPOptionsOverride{});
+ }
+ }
- return BO.recreateBinOp(S, LHSRes, RHSRes);
+ if (Out.isUnset()) {
+ Satisfaction.IsSatisfied = Conjunction;
+ Out = S.BuildEmptyCXXFoldExpr(FE->getBeginLoc(), FE->getOperator());
+ }
+ return Out;
+}
+
+template <typename ConstraintEvaluator>
+static ExprResult
+calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
+ ConstraintSatisfaction &Satisfaction,
+ ConstraintEvaluator &&Evaluator) {
+ ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
+
+ if (LogicalBinOp BO = ConstraintExpr) {
+
+ return calculateConstraintSatisfaction(
+ S, BO.getLHS(), BO.getOp(), BO.getRHS(), Satisfaction, Evaluator);
}
if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr)) {
@@ -243,11 +339,19 @@ calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
// evaluate these as if the cleanups didn't exist.
return calculateConstraintSatisfaction(
S, C->getSubExpr(), Satisfaction,
- std::forward<AtomicEvaluator>(Evaluator));
+ std::forward<ConstraintEvaluator>(Evaluator));
+ }
+
+ if (auto *FE = dyn_cast<CXXFoldExpr>(ConstraintExpr);
+ FE && S.getLangOpts().CPlusPlus26 &&
+ (FE->getOperator() == BinaryOperatorKind::BO_LAnd ||
+ FE->getOperator() == BinaryOperatorKind::BO_LOr)) {
+ return calculateConstraintSatisfaction(S, FE, Satisfaction, Evaluator);
}
// An atomic constraint expression
- ExprResult SubstitutedAtomicExpr = Evaluator(ConstraintExpr);
+ ExprResult SubstitutedAtomicExpr =
+ Evaluator.EvaluateAtomicConstraint(ConstraintExpr);
if (SubstitutedAtomicExpr.isInvalid())
return ExprError();
@@ -336,92 +440,133 @@ static ExprResult calculateConstraintSatisfaction(
Sema &S, const NamedDecl *Template, SourceLocation TemplateNameLoc,
const MultiLevelTemplateArgumentList &MLTAL, const Expr *ConstraintExpr,
ConstraintSatisfaction &Satisfaction) {
- return calculateConstraintSatisfaction(
- S, ConstraintExpr, Satisfaction, [&](const Expr *AtomicExpr) {
- EnterExpressionEvaluationContext ConstantEvaluated(
- S, Sema::ExpressionEvaluationContext::ConstantEvaluated,
- Sema::ReuseLambdaContextDecl);
-
- // Atomic constraint - substitute arguments and check satisfaction.
- ExprResult SubstitutedExpression;
- {
- TemplateDeductionInfo Info(TemplateNameLoc);
- Sema::InstantiatingTemplate Inst(S, AtomicExpr->getBeginLoc(),
- Sema::InstantiatingTemplate::ConstraintSubstitution{},
- const_cast<NamedDecl *>(Template), Info,
- AtomicExpr->getSourceRange());
- if (Inst.isInvalid())
+
+ struct ConstraintEvaluator {
+ Sema &S;
+ const NamedDecl *Template;
+ SourceLocation TemplateNameLoc;
+ const MultiLevelTemplateArgumentList &MLTAL;
+ ConstraintSatisfaction &Satisfaction;
+
+ ExprResult EvaluateAtomicConstraint(const Expr *AtomicExpr) {
+ EnterExpressionEvaluationContext ConstantEvaluated(
+ S, Sema::ExpressionEvaluationContext::ConstantEvaluated,
+ Sema::ReuseLambdaContextDecl);
+
+ // Atomic constraint - substitute arguments and check satisfaction.
+ ExprResult SubstitutedExpression;
+ {
+ TemplateDeductionInfo Info(TemplateNameLoc);
+ Sema::InstantiatingTemplate Inst(
+ S, AtomicExpr->getBeginLoc(),
+ Sema::InstantiatingTemplate::ConstraintSubstitution{},
+ const_cast<NamedDecl *>(Template), Info,
+ AtomicExpr->getSourceRange());
+ if (Inst.isInvalid())
+ return ExprError();
+
+ llvm::FoldingSetNodeID ID;
+ if (Template &&
+ DiagRecursiveConstraintEval(S, ID, Template, AtomicExpr, MLTAL)) {
+ Satisfaction.IsSatisfied = false;
+ Satisfaction.ContainsErrors = true;
+ return ExprEmpty();
+ }
+
+ SatisfactionStackRAII StackRAII(S, Template, ID);
+
+ // We do not want error diagnostics escaping here.
+ Sema::SFINAETrap Trap(S);
+ SubstitutedExpression =
+ S.SubstConstraintExpr(const_cast<Expr *>(AtomicExpr), MLTAL);
+
+ if (SubstitutedExpression.isInvalid() || Trap.hasErrorOccurred()) {
+ // C++2a [temp.constr.atomic]p1
+ // ...If substitution results in an invalid type or expression, the
+ // constraint is not satisfied.
+ if (!Trap.hasErrorOccurred())
+ // A non-SFINAE error has occurred as a result of this
+ // substitution.
return ExprError();
- llvm::FoldingSetNodeID ID;
- if (Template &&
- DiagRecursiveConstraintEval(S, ID, Template, AtomicExpr, MLTAL)) {
- Satisfaction.IsSatisfied = false;
- Satisfaction.ContainsErrors = true;
- return ExprEmpty();
- }
-
- SatisfactionStackRAII StackRAII(S, Template, ID);
-
- // We do not want error diagnostics escaping here.
- Sema::SFINAETrap Trap(S);
- SubstitutedExpression =
- S.SubstConstraintExpr(const_cast<Expr *>(AtomicExpr), MLTAL);
-
- if (SubstitutedExpression.isInvalid() || Trap.hasErrorOccurred()) {
- // C++2a [temp.constr.atomic]p1
- // ...If substitution results in an invalid type or expression, the
- // constraint is not satisfied.
- if (!Trap.hasErrorOccurred())
- // A non-SFINAE error has occurred as a result of this
- // substitution.
- return ExprError();
-
- PartialDiagnosticAt SubstDiag{SourceLocation(),
- PartialDiagnostic::NullDiagnostic()};
- Info.takeSFINAEDiagnostic(SubstDiag);
- // FIXME: Concepts: This is an unfortunate consequence of there
- // being no serialization code for PartialDiagnostics and the fact
- // that serializing them would likely take a lot more storage than
- // just storing them as strings. We would still like, in the
- // future, to serialize the proper PartialDiagnostic as serializing
- // it as a string defeats the purpose of the diagnostic mechanism.
- SmallString<128> DiagString;
- DiagString = ": ";
- SubstDiag.second.EmitToString(S.getDiagnostics(), DiagString);
- unsigned MessageSize = DiagString.size();
- char *Mem = new (S.Context) char[MessageSize];
- memcpy(Mem, DiagString.c_str(), MessageSize);
- Satisfaction.Details.emplace_back(
- AtomicExpr,
- new (S.Context) ConstraintSatisfaction::SubstitutionDiagnostic{
- SubstDiag.first, StringRef(Mem, MessageSize)});
- Satisfaction.IsSatisfied = false;
- return ExprEmpty();
- }
+ PartialDiagnosticAt SubstDiag{SourceLocation(),
+ PartialDiagnostic::NullDiagnostic()};
+ Info.takeSFINAEDiagnostic(SubstDiag);
+ // FIXME: Concepts: This is an unfortunate consequence of there
+ // being no serialization code for PartialDiagnostics and the fact
+ // that serializing them would likely take a lot more storage than
+ // just storing them as strings. We would still like, in the
+ // future, to serialize the proper PartialDiagnostic as serializing
+ // it as a string defeats the purpose of the diagnostic mechanism.
+ SmallString<128> DiagString;
+ DiagString = ": ";
+ SubstDiag.second.EmitToString(S.getDiagnostics(), DiagString);
+ unsigned MessageSize = DiagString.size();
+ char *Mem = new (S.Context) char[MessageSize];
+ memcpy(Mem, DiagString.c_str(), MessageSize);
+ Satisfaction.Details.emplace_back(
+ AtomicExpr,
+ new (S.Context) ConstraintSatisfaction::SubstitutionDiagnostic{
+ SubstDiag.first, StringRef(Mem, MessageSize)});
+ Satisfaction.IsSatisfied = false;
+ return ExprEmpty();
}
+ }
- if (!S.CheckConstraintExpression(SubstitutedExpression.get()))
- return ExprError();
+ if (!S.CheckConstraintExpression(SubstitutedExpression.get()))
+ return ExprError();
+
+ // [temp.constr.atomic]p3: To determine if an atomic constraint is
+ // satisfied, the parameter mapping and template arguments are first
+ // substituted into its expression. If substitution results in an
+ // invalid type or expression, the constraint is not satisfied.
+ // Otherwise, the lvalue-to-rvalue conversion is performed if necessary,
+ // and E shall be a constant expression of type bool.
+ //
+ // Perform the L to R Value conversion if necessary. We do so for all
+ // non-PRValue categories, else we fail to extend the lifetime of
+ // temporaries, and that fails the constant expression check.
+ if (!SubstitutedExpression.get()->isPRValue())
+ SubstitutedExpression = ImplicitCastExpr::Create(
+ S.Context, SubstitutedExpression.get()->getType(),
+ CK_LValueToRValue, SubstitutedExpression.get(),
+ /*BasePath=*/nullptr, VK_PRValue, FPOptionsOverride());
+
+ return SubstitutedExpression;
+ }
- // [temp.constr.atomic]p3: To determine if an atomic constraint is
- // satisfied, the parameter mapping and template arguments are first
- // substituted into its expression. If substitution results in an
- // invalid type or expression, the constraint is not satisfied.
- // Otherwise, the lvalue-to-rvalue conversion is performed if necessary,
- // and E shall be a constant expression of type bool.
- //
- // Perform the L to R Value conversion if necessary. We do so for all
- // non-PRValue categories, else we fail to extend the lifetime of
- // temporaries, and that fails the constant expression check.
- if (!SubstitutedExpression.get()->isPRValue())
- SubstitutedExpression = ImplicitCastExpr::Create(
- S.Context, SubstitutedExpression.get()->getType(),
- CK_LValueToRValue, SubstitutedExpression.get(),
- /*BasePath=*/nullptr, VK_PRValue, FPOptionsOverride());
-
- return SubstitutedExpression;
- });
+ std::optional<unsigned>
+ EvaluateFoldExpandedConstraintSize(const CXXFoldExpr *FE) {
+ Expr *Pattern = FE->getPattern();
+
+ SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+ S.collectUnexpandedParameterPacks(Pattern, Unexpanded);
+ assert(!Unexpanded.empty() && "Pack expansion without parameter packs?");
+ bool Expand = true;
+ bool RetainExpansion = false;
+ std::optional<unsigned> OrigNumExpansions = FE->getNumExpansions(),
+ NumExpansions = OrigNumExpansions;
+ if (S.CheckParameterPacksForExpansion(
+ FE->getEllipsisLoc(), Pattern->getSourceRange(), Unexpanded,
+ MLTAL, Expand, RetainExpansion, NumExpansions) ||
+ !Expand || RetainExpansion)
+ return std::nullopt;
+
+ if (NumExpansions && S.getLangOpts().BracketDepth < NumExpansions) {
+ S.Diag(FE->getEllipsisLoc(),
+ clang::diag::err_fold_expression_limit_exceeded)
+ << *NumExpansions << S.getLangOpts().BracketDepth
+ << FE->getSourceRange();
+ S.Diag(FE->getEllipsisLoc(), diag::note_bracket_depth);
+ return std::nullopt;
+ }
+ return NumExpansions;
+ }
+ };
+
+ return calculateConstraintSatisfaction(
+ S, ConstraintExpr, Satisfaction,
+ ConstraintEvaluator{S, Template, TemplateNameLoc, MLTAL, Satisfaction});
}
static bool CheckConstraintSatisfaction(
@@ -537,13 +682,21 @@ bool Sema::CheckConstraintSatisfaction(
bool Sema::CheckConstraintSatisfaction(const Expr *ConstraintExpr,
ConstraintSatisfaction &Satisfaction) {
- return calculateConstraintSatisfaction(
- *this, ConstraintExpr, Satisfaction,
- [this](const Expr *AtomicExpr) -> ExprResult {
- // We only do this to immitate lvalue-to-rvalue conversion.
- return PerformContextuallyConvertToBool(
- const_cast<Expr *>(AtomicExpr));
- })
+
+ struct ConstraintEvaluator {
+ Sema &S;
+ ExprResult EvaluateAtomicConstraint(const Expr *AtomicExpr) {
+ return S.PerformContextuallyConvertToBool(const_cast<Expr *>(AtomicExpr));
+ }
+
+ std::optional<unsigned>
+ EvaluateFoldExpandedConstraintSize(const CXXFoldExpr *FE) {
+ return 0;
+ }
+ };
+
+ return calculateConstraintSatisfaction(*this, ConstraintExpr, Satisfaction,
+ ConstraintEvaluator{*this})
.isInvalid();
}
@@ -1238,6 +1391,13 @@ Sema::getNormalizedAssociatedConstraints(
return CacheEntry->second;
}
+const NormalizedConstraint *clang::getNormalizedAssociatedConstraints(
+ Sema &S, NamedDecl *ConstrainedDecl,
+ ArrayRef<const Expr *> AssociatedConstraints) {
+ return S.getNormalizedAssociatedConstraints(ConstrainedDecl,
+ AssociatedConstraints);
+}
+
static bool
substituteParameterMappings(Sema &S, NormalizedConstraint &N,
ConceptDecl *Concept,
@@ -1316,6 +1476,33 @@ static bool substituteParameterMappings(Sema &S, NormalizedConstraint &N,
CSE->getTemplateArgsAsWritten());
}
+NormalizedConstraint::NormalizedConstraint(ASTContext &C,
+ NormalizedConstraint LHS,
+ NormalizedConstraint RHS,
+ CompoundConstraintKind Kind)
+ : Constraint{CompoundConstraint{
+ new(C) std::pair<NormalizedConstraint, NormalizedConstraint>{
+ std::move(LHS), std::move(RHS)},
+ Kind}} {}
+
+NormalizedConstraint::NormalizedConstraint(ASTContext &C,
+ const NormalizedConstraint &Other) {
+ if (Other.isAtomic()) {
+ Constraint = new (C) AtomicConstraint(*Other.getAtomicConstraint());
+ } else if (Other.isFoldExpanded()) {
+ Constraint = new (C) FoldExpandedConstraint(
+ Other.getFoldExpandedConstraint()->Kind,
+ NormalizedConstraint(C, Other.getFoldExpandedConstraint()->Constraint),
+ Other.getFoldExpandedConstraint()->Pattern);
+ } else {
+ Constraint = CompoundConstraint(
+ new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
+ NormalizedConstraint(C, Other.getLHS()),
+ NormalizedConstraint(C, Other.getRHS())},
+ Other.getCompoundKind());
+ }
+}
+
std::optional<NormalizedConstraint>
NormalizedConstraint::fromConstraintExprs(Sema &S, NamedDecl *D,
ArrayRef<const Expr *> E) {
@@ -1390,17 +1577,82 @@ NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
return std::nullopt;
return New;
+ } else if (auto *FE = dyn_cast<const CXXFoldExpr>(E);
+ FE && S.getLangOpts().CPlusPlus26 &&
+ (FE->getOperator() == BinaryOperatorKind::BO_LAnd ||
+ FE->getOperator() == BinaryOperatorKind::BO_LOr)) {
+
+ FoldExpandedConstraint::FoldOperatorKind Kind =
+ FE->getOperator() == BinaryOperatorKind::BO_LAnd
+ ? FoldExpandedConstraint::FoldOperatorKind::FoAnd
+ : FoldExpandedConstraint::FoldOperatorKind::FoOr;
+
+ if (FE->getInit()) {
+ auto LHS = fromConstraintExpr(S, D, FE->getLHS());
+ auto RHS = fromConstraintExpr(S, D, FE->getRHS());
+ if (!LHS || !RHS)
+ return std::nullopt;
+
+ if (FE->isRightFold())
+ RHS = NormalizedConstraint{new (S.Context) FoldExpandedConstraint{
+ Kind, std::move(*RHS), FE->getPattern()}};
+ else
+ LHS = NormalizedConstraint{new (S.Context) FoldExpandedConstraint{
+ Kind, std::move(*LHS), FE->getPattern()}};
+
+ return NormalizedConstraint(
+ S.Context, std::move(*LHS), std::move(*RHS),
+ FE->getOperator() == BinaryOperatorKind::BO_LAnd ? CCK_Conjunction
+ : CCK_Disjunction);
+ }
+ auto Sub = fromConstraintExpr(S, D, FE->getPattern());
+ return NormalizedConstraint{new (S.Context) FoldExpandedConstraint{
+ Kind, std::move(*Sub), FE->getPattern()}};
}
+
return NormalizedConstraint{new (S.Context) AtomicConstraint(S, E)};
}
-using NormalForm =
- llvm::SmallVector<llvm::SmallVector<AtomicConstraint *, 2>, 4>;
+/*static void CollectConstraintParams(const AtomicConstraint& C,
+SmallVectorImpl<UnexpandedParameterPack> & Packs) {
+ Sema::collectUnexpandedParameterPacks(const_cast<Expr*>(C.ConstraintExpr),
+Packs);
+}
-static NormalForm makeCNF(const NormalizedConstraint &Normalized) {
+static void CollectConstraintParams(const NormalizedConstraint& C,
+SmallVectorImpl<UnexpandedParameterPack> & Packs) { if(C.isAtomic())
+ CollectConstraintParams(*C.getAtomicConstraint(), Packs);
+ else {
+ CollectConstraintParams(C.getLHS(), Packs);
+ CollectConstraintParams(C.getRHS(), Packs);
+ }
+}
+*/
+
+bool FoldExpandedConstraint::AreSubsumptionElligible(
+ const FoldExpandedConstraint &A, const FoldExpandedConstraint &B) {
+ llvm::SmallVector<UnexpandedParameterPack> APacks, BPacks;
+ Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(A.Pattern), APacks);
+ Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(B.Pattern), BPacks);
+
+ for (const UnexpandedParameterPack &APack : APacks) {
+ std::pair<unsigned, unsigned> DepthAndIndex = getDepthAndIndex(APack);
+ auto it = llvm::find_if(BPacks, [&](const UnexpandedParameterPack &BPack) {
+ return getDepthAndIndex(BPack) == DepthAndIndex;
+ });
+ if (it != BPacks.end())
+ return true;
+ }
+ return false;
+}
+
+NormalForm clang::makeCNF(const NormalizedConstraint &Normalized) {
if (Normalized.isAtomic())
return {{Normalized.getAtomicConstraint()}};
+ else if (Normalized.isFoldExpanded())
+ return {{Normalized.getFoldExpandedConstraint()}};
+
NormalForm LCNF = makeCNF(Normalized.getLHS());
NormalForm RCNF = makeCNF(Normalized.getRHS());
if (Normalized.getCompoundKind() == NormalizedConstraint::CCK_Conjunction) {
@@ -1426,10 +1678,13 @@ static NormalForm makeCNF(const NormalizedConstraint &Normalized) {
return Res;
}
-static NormalForm makeDNF(const NormalizedConstraint &Normalized) {
+NormalForm clang::makeDNF(const NormalizedConstraint &Normalized) {
if (Normalized.isAtomic())
return {{Normalized.getAtomicConstraint()}};
+ else if (Normalized.isFoldExpanded())
+ return {{Normalized.getFoldExpandedConstraint()}};
+
NormalForm LDNF = makeDNF(Normalized.getLHS());
NormalForm RDNF = makeDNF(Normalized.getRHS());
if (Normalized.getCompoundKind() == NormalizedConstraint::CCK_Disjunction) {
@@ -1456,60 +1711,6 @@ static NormalForm makeDNF(const NormalizedConstraint &Normalized) {
return Res;
}
-template<typename AtomicSubsumptionEvaluator>
-static bool subsumes(const NormalForm &PDNF, const NormalForm &QCNF,
- AtomicSubsumptionEvaluator E) {
- // C++ [temp.constr.order] p2
- // Then, P subsumes Q if and only if, for every disjunctive clause Pi in the
- // disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in
- // the conjuctive normal form of Q, where [...]
- for (const auto &Pi : PDNF) {
- for (const auto &Qj : QCNF) {
- // C++ [temp.constr.order] p2
- // - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if
- // and only if there exists an atomic constraint Pia in Pi for which
- // there exists an atomic constraint, Qjb, in Qj such that Pia
- // subsumes Qjb.
- bool Found = false;
- for (const AtomicConstraint *Pia : Pi) {
- for (const AtomicConstraint *Qjb : Qj) {
- if (E(*Pia, *Qjb)) {
- Found = true;
- break;
- }
- }
- if (Found)
- break;
- }
- if (!Found)
- return false;
- }
- }
- return true;
-}
-
-template<typename AtomicSubsumptionEvaluator>
-static bool subsumes(Sema &S, NamedDecl *DP, ArrayRef<const Expr *> P,
- NamedDecl *DQ, ArrayRef<const Expr *> Q, bool &Subsumes,
- AtomicSubsumptionEvaluator E) {
- // C++ [temp.constr.order] p2
- // In order to determine if a constraint P subsumes a constraint Q, P is
- // transformed into disjunctive normal form, and Q is transformed into
- // conjunctive normal form. [...]
- auto *PNormalized = S.getNormalizedAssociatedConstraints(DP, P);
- if (!PNormalized)
- return true;
- const NormalForm PDNF = makeDNF(*PNormalized);
-
- auto *QNormalized = S.getNormalizedAssociatedConstraints(DQ, Q);
- if (!QNormalized)
- return true;
- const NormalForm QCNF = makeCNF(*QNormalized);
-
- Subsumes = subsumes(PDNF, QCNF, E);
- return false;
-}
-
bool Sema::IsAtLeastAsConstrained(NamedDecl *D1,
MutableArrayRef<const Expr *> AC1,
NamedDecl *D2,
@@ -1562,10 +1763,11 @@ bool Sema::IsAtLeastAsConstrained(NamedDecl *D1,
}
}
- if (subsumes(*this, D1, AC1, D2, AC2, Result,
- [this] (const AtomicConstraint &A, const AtomicConstraint &B) {
- return A.subsumes(Context, B);
- }))
+ if (clang::subsumes(
+ *this, D1, AC1, D2, AC2, Result,
+ [this](const AtomicConstraint &A, const AtomicConstraint &B) {
+ return A.subsumes(Context, B);
+ }))
return true;
SubsumptionCache.try_emplace(Key, Result);
return false;
@@ -1622,10 +1824,12 @@ bool Sema::MaybeEmitAmbiguousAtomicConstraintsDiagnostic(NamedDecl *D1,
const NormalForm DNF2 = makeDNF(*Normalized2);
const NormalForm CNF2 = makeCNF(*Normalized2);
- bool Is1AtLeastAs2Normally = subsumes(DNF1, CNF2, NormalExprEvaluator);
- bool Is2AtLeastAs1Normally = subsumes(DNF2, CNF1, NormalExprEvaluator);
- bool Is1AtLeastAs2 = subsumes(DNF1, CNF2, IdenticalExprEvaluator);
- bool Is2AtLeastAs1 = subsumes(DNF2, CNF1, IdenticalExprEvaluator);
+ bool Is1AtLeastAs2Normally =
+ clang::subsumes(DNF1, CNF2, NormalExprEvaluator);
+ bool Is2AtLeastAs1Normally =
+ clang::subsumes(DNF2, CNF1, NormalExprEvaluator);
+ bool Is1AtLeastAs2 = clang::subsumes(DNF1, CNF2, IdenticalExprEvaluator);
+ bool Is2AtLeastAs1 = clang::subsumes(DNF2, CNF1, IdenticalExprEvaluator);
if (Is1AtLeastAs2 == Is1AtLeastAs2Normally &&
Is2AtLeastAs1 == Is2AtLeastAs1Normally)
// Same result - no ambiguity was caused by identical atomic expressions.
diff --git a/clang/lib/Sema/SemaTemplateVariadic.cpp b/clang/lib/Sema/SemaTemplateVariadic.cpp
index 6df7f2223d267..3d4ccaf68c700 100644
--- a/clang/lib/Sema/SemaTemplateVariadic.cpp
+++ b/clang/lib/Sema/SemaTemplateVariadic.cpp
@@ -566,6 +566,10 @@ void Sema::collectUnexpandedParameterPacks(
.TraverseDeclarationNameInfo(NameInfo);
}
+void Sema::collectUnexpandedParameterPacks(
+ Expr *E, SmallVectorImpl<UnexpandedParameterPack> &Unexpanded) {
+ CollectUnexpandedParameterPacksVisitor(Unexpanded).TraverseStmt(E);
+}
ParsedTemplateArgument
Sema::ActOnPackExpansion(const ParsedTemplateArgument &Arg,
diff --git a/clang/test/SemaCXX/cxx2c-fold-exprs.cpp b/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
new file mode 100644
index 0000000000000..d267f334aedd6
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
@@ -0,0 +1,239 @@
+// RUN: %clang_cc1 -std=c++2c -verify %s
+
+template <class T> concept A = true;
+template <class T> concept C = A<T> && true;
+template <class T> concept D = A<T> && __is_same(T, int);
+
+
+template <class T> requires (A<T>)
+constexpr int f(T) { return 0; };
+template <class... T> requires (C<T> && ...)
+constexpr int f(T...) { return 1; };
+
+static_assert(f(0) == 0);
+static_assert(f(1) == 0);
+
+
+template <class... T> requires (A<T> && ...)
+constexpr int g(T...) { return 0; };
+template <class... T> requires (C<T> && ...)
+constexpr int g(T...) { return 1; };
+
+static_assert(g(0) == 1);
+static_assert(g() == 1);
+static_assert(g(1, 2) == 1);
+
+
+
+template <class... T> requires (A<T> && ...)
+constexpr int h(T...) { return 0; }; // expected-note {{candidate}}
+template <class... T> requires (C<T> || ...)
+constexpr int h(T...) { return 1; }; // expected-note {{candidate}}
+
+static_assert(h(0) == 1); // expected-error {{call to 'h' is ambiguous}}
+
+template <class... T> requires (A<T> || ...)
+constexpr int i(T...) { return 0; }; // expected-note {{candidate}}
+template <class... T> requires (C<T> && ...)
+constexpr int i(T...) { return 1; }; // expected-note {{candidate}}
+
+static_assert(i(0) == 1); // expected-error {{call to 'i' is ambiguous}}
+
+
+template <class... T> requires (A<T> || ... || true)
+constexpr int j(T...) { return 0; };
+template <class... T> requires (C<T> && ... && true)
+constexpr int j(T...) { return 1; };
+
+static_assert(j(0) == 1);
+static_assert(j() == 1);
+
+
+
+template <class... T> requires (A<T> || ...)
+constexpr int k(T...) { return 0; }; // expected-note {{candidate template ignored: constraints not satisfied [with T = <>]}}
+template <class... T> requires (C<T> || ...)
+constexpr int k(T...) { return 1; }; // expected-note {{candidate template ignored: constraints not satisfied [with T = <>]}}
+
+static_assert(k(0) == 1);
+static_assert(k() == 0); // expected-error {{no matching function for call to 'k'}}
+static_assert(k(1, 2) == 1);
+
+
+consteval int terse(A auto...) {return 1;}
+consteval int terse(D auto...) {return 2;}
+
+static_assert(terse() == 2);
+static_assert(terse(0, 0) == 2);
+static_assert(terse(0L, 0) == 1);
+
+template <A... T>
+consteval int tpl_head(A auto...) {return 1;}
+template <D... T>
+consteval int tpl_head(D auto...) {return 2;}
+
+static_assert(tpl_head() == 2);
+static_assert(tpl_head(0, 0) == 2);
+static_assert(tpl_head(0L, 0) == 1);
+
+
+namespace equivalence {
+
+template <typename... T>
+struct S {
+ template <typename... U>
+ void f() requires (A<U> && ...);
+ template <typename... U>
+ void f() requires (C<U> && ...);
+
+ template <typename... U>
+ void g() requires (A<T> && ...);
+ template <typename... U>
+ void g() requires (C<T> && ...);
+
+ template <typename... U>
+ void h() requires (A<U> && ...); // expected-note {{candidate}}
+ template <typename... U>
+ void h() requires (C<T> && ...); // expected-note {{candidate}}
+};
+
+void test() {
+ S<int>{}.f<int>();
+ S<int>{}.g<int>();
+ S<int>{}.h<int>(); // expected-error {{call to member function 'h' is ambiguous}}
+}
+
+
+}
+
+namespace substitution {
+ struct S {
+ using type = int;
+};
+
+template <typename... T>
+consteval int And1() requires (C<typename T::type> && ...) { // #and1
+ return 1;
+}
+
+template <typename T, typename... U>
+consteval int And2() requires (C<typename U::type> && ... && C<typename T::type>) { // #and2
+ return 2;
+}
+
+template <typename T, typename... U>
+consteval int And3() requires (C<typename T::type> && ... && C<typename U::type>) { // #and3
+ return 3;
+}
+
+template <typename... T>
+consteval int Or1() requires (C<typename T::type> || ...) { // #or1
+ return 1;
+}
+
+template <typename T, typename... U>
+consteval int Or2() requires (C<typename U::type> || ... || C<typename T::type>) { // #or2
+ return 2;
+}
+
+template <typename T, typename... U>
+consteval int Or3() requires (C<typename T::type> || ... || C<typename U::type>) { // #or3
+ return 3;
+}
+
+static_assert(And1<>() == 1);
+static_assert(And1<S>() == 1);
+static_assert(And1<S, S>() == 1);
+static_assert(And1<int>() == 1); // expected-error {{no matching function for call to 'And1'}}
+ // expected-note@#and1 {{candidate template ignored: constraints not satisfied}}
+ // expected-note@#and1 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And1<S, int>() == 1); // expected-error {{no matching function for call to 'And1'}}
+ // expected-note@#and1 {{candidate template ignored: constraints not satisfied}}
+ // expected-note@#and1 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And1<int, S>() == 1); // expected-error {{no matching function for call to 'And1'}}
+ // expected-note@#and1 {{candidate template ignored: constraints not satisfied}}
+ // expected-note@#and1 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And2<S>() == 2);
+static_assert(And2<S, S>() == 2);
+static_assert(And2<int>() == 2);
+
+static_assert(And2<int, int>() == 2); // expected-error {{no matching function for call to 'And2'}}
+ // expected-note@#and2 {{candidate template ignored: constraints not satisfied}}
+ // expected-note@#and2 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And2<S, int>() == 2); // expected-error {{no matching function for call to 'And2'}}
+ // expected-note@#and2 {{candidate template ignored: constraints not satisfied}}
+ // expected-note@#and2 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And2<int, S>() == 2); // expected-error {{no matching function for call to 'And2'}}
+ // expected-note@#and2 {{candidate template ignored: constraints not satisfied}}
+ // expected-note@#and2 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And3<S>() == 3);
+static_assert(And3<S, S>() == 3);
+static_assert(And3<int>() == 3); // expected-error {{no matching function for call to 'And3'}}
+ // expected-note@#and3 {{candidate template ignored: constraints not satisfied}}
+ // expected-note@#and3 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And3<int, int>() == 3); // expected-error {{no matching function for call to 'And3'}}
+ // expected-note@#and3 {{candidate template ignored: constraints not satisfied}}
+ // expected-note@#and3 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And3<S, int>() == 3); // expected-error {{no matching function for call to 'And3'}}
+ // expected-note@#and3 {{candidate template ignored: constraints not satisfied}}
+ // expected-note@#and3 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And3<int, S>() == 3); // expected-error {{no matching function for call to 'And3'}}
+ // expected-note@#and3 {{candidate template ignored: constraints not satisfied}}
+ // expected-note@#and3 {{because substituted constraint expression is ill-formed}}
+
+
+static_assert(Or1<>() == 1); // expected-error {{no matching function for call to 'Or1'}}
+ // expected-note@#or1 {{candidate template ignored: constraints not satisfied}}
+static_assert(Or1<S>() == 1);
+static_assert(Or1<int, S>() == 1);
+static_assert(Or1<S, int>() == 1);
+static_assert(Or1<S, S>() == 1);
+static_assert(Or1<int>() == 1); // expected-error {{no matching function for call to 'Or1'}}
+ // expected-note@#or1 {{candidate template ignored: constraints not satisfied}} \
+ // expected-note@#or1 {{because substituted constraint expression is ill-formed}}
+
+
+static_assert(Or2<S>() == 2);
+static_assert(Or2<int, S>() == 2);
+static_assert(Or2<S, int>() == 2);
+static_assert(Or2<S, S>() == 2);
+static_assert(Or2<int>() == 2); // expected-error {{no matching function for call to 'Or2'}}
+ // expected-note@#or2 {{candidate template ignored: constraints not satisfied}} \
+ // expected-note@#or2 {{because substituted constraint expression is ill-formed}}
+
+static_assert(Or3<S>() == 3);
+static_assert(Or3<int, S>() == 3);
+static_assert(Or3<S, int>() == 3);
+static_assert(Or3<S, S>() == 3);
+static_assert(Or3<int>() == 3); // expected-error {{no matching function for call to 'Or3'}}
+ // expected-note@#or3 {{candidate template ignored: constraints not satisfied}} \
+ // expected-note@#or3 {{because substituted constraint expression is ill-formed}}
+}
+
+namespace bool_conversion_break {
+
+template <typename ...V> struct A;
+struct Thingy {
+ static constexpr int compare(const Thingy&) {return 1;}
+};
+template <typename ...T, typename ...U>
+void f(A<T ...> *, A<U ...> *) // expected-note {{candidate template ignored: failed template argument deduction}}
+requires (T::compare(U{}) && ...); // expected-error {{atomic constraint must be of type 'bool' (found 'int')}}
+
+void g() {
+ A<Thingy, Thingy> *ap;
+ f(ap, ap); // expected-error{{no matching function for call to 'f'}} \
+ // expected-note {{while checking constraint satisfaction}} \
+ // expected-note {{in instantiation of function template specialization}}
+}
+
+}
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 27e2213e54caa..a6ded8be3ae9e 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -218,7 +218,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>Ordering of constraints involving fold expressions</td>
<td><a href="https://wg21.link/P2963R3">P2963R3</a></td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 19</td>
</tr>
<tr>
<td>Structured binding declaration as a condition</td>
More information about the cfe-commits
mailing list