[clang] [Clang][Sema] Reject template arguments not equivalent to their copies (part of P2308R1) (PR #193754)

Yanzuo Liu via cfe-commits cfe-commits at lists.llvm.org
Mon Jul 27 20:04:07 PDT 2026


================
@@ -7130,6 +7130,93 @@ static bool CheckTemplateArgumentPointerToMember(
   return true;
 }
 
+// P2308R1  C++26 [temp.arg.nontype]p4:
+//   ... If, for the initialization from any candidate initializer,
+//     - the initialization would be ill-formed, or
+//     - ...
+//     - the initialization would cause P to not be
+//       template-argument-equivalent ([temp.type]) to v,
+//   the program is ill-formed.
+//
+// Returns `false` if they are template-argument-equivalent, `true` if the
+// initialization fails or they are not template-argument-equivalent.
+static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param,
+                                                 QualType ParamType,
+                                                 const APValue &Value,
+                                                 SourceLocation ArgLoc) {
+  assert(ParamType->isRecordType() && "no need to check copy equivalence");
+
+  // Fast path. Try to find the copy constructor which will be selected by
+  // overload resolution. Trivial copy constructor performs per-element copy.
+  if (auto *CXXRecord = ParamType->castAsCXXRecordDecl();
+      !CXXRecord->hasUserDeclaredConstructor()) {
+
+    if (CXXRecord->hasTrivialCopyConstructor() &&
+        !CXXRecord->needsOverloadResolutionForCopyConstructor())
+      return false;
+
+  } else {
+
+    // A template parameter with this type has been initialized,
+    // so implicitly-declared functions should be declared,
+    // and explicit(bool) should be resolved.
+    if (llvm::any_of(CXXRecord->ctors(), [](CXXConstructorDecl *Ctor) {
+          unsigned Quals;
+          // TODO: After correctly marking deleted methods as ineligible,
+          // `!Ctor->isDeleted()` is redundant and can be dropped.
+          // See comment in `SetEligibleMethods` in SemaDecl.cpp.
+          return Ctor->isCopyConstructor(Quals) && Quals == Qualifiers::Const &&
+                 !Ctor->isIneligibleOrNotSelected() && Ctor->isTrivial() &&
+                 !Ctor->isDeleted() && !Ctor->isExplicit();
+        }))
+      return false;
+  }
+
+  SourceLocation ParamLoc = Param->getLocation();
+
+  // Instead of creating a variable (C++26 [temp.arg.nontype]p3),
+  // create a template parameter object to represent the candidate initializer.
+  // They are equivalent when creating a copy.
+  auto *CandidateInitializer =
+      S.BuildDeclRefExpr(S.Context.getTemplateParamObjectDecl(ParamType, Value),
+                         ParamType.withConst(), VK_LValue, ArgLoc);
+  InitializationKind Kind = InitializationKind::CreateForInit(
+      ArgLoc, /*DirectInit=*/false, CandidateInitializer);
+  Expr *Inits[1] = {CandidateInitializer};
+  InitializedEntity Entity =
+      InitializedEntity::InitializeTemplateParameter(ParamType, Param);
+  InitializationSequence InitSeq(S, Entity, Kind, Inits);
+  ExprResult Result = InitSeq.Perform(S, Entity, Kind, Inits);
+  if (Result.isInvalid())
+    return S.Diag(ParamLoc, diag::note_template_arg_requires_copy);
+
+  Result = S.ActOnConstantExpression(Result);
+  Result = S.ActOnFinishFullExpr(AssertSuccess(Result), ArgLoc,
+                                 /*DiscardedValue=*/false,
+                                 /*IsConstexpr=*/true,
+                                 /*IsTemplateArgument=*/true);
+
+  APValue ValueAfterCopy, PreNarrowingValue;
+  Result = S.EvaluateConvertedConstantExpression(
+      AssertSuccess(Result), ParamType, ValueAfterCopy, CCEKind::TemplateArg,
+      /*RequireInt=*/false, PreNarrowingValue);
+  if (Result.isInvalid())
+    return S.Diag(ParamLoc, diag::note_template_arg_requires_copy);
----------------
zwuis wrote:

```cpp
template <typename> constexpr bool False = false;
template <typename T = int> struct A {
  A() = default;
  constexpr A(const A&) {
    static_assert(False<T>);
  }
};

template <A> struct B {};
B<{}> b;
```

The diagnostics of the code above:

```txt
<source>:5:19: error: static assertion failed due to requirement 'False<int>'
    5 |     static_assert(False<T>);
      |                   ^~~~~~~~
#Mark
<source>:10:3: note: in instantiation of member function 'A<>::A' requested here
   10 | B<{}> b;
      |   ^
1 error generated.
```

Thanks to error recovery, the return value is never `ExprError()`, so the note is not emitted, which makes users harder to understand what happens. If we use `CodeSynthesisContext`, the note will be emitted at `#Mark`.

https://github.com/llvm/llvm-project/pull/193754


More information about the cfe-commits mailing list