[clang] [clang][Sema] support block pointers as non-type template parameters (PR #191694)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Apr 12 02:21:40 PDT 2026
https://github.com/Serosh-commits created https://github.com/llvm/llvm-project/pull/191694
allow block pointers to be used as template parameters in C++20.
Add mangling for block literals in template arguments.
Let ODR hashing see block expressions so they don't cause a crash.
Fix a crash by making sure block names decay to pointers.
Allow matching block pointer types even if they have different const qualifiers.
>From 0a615a8f2e6b301485a65ee30f43bfe49276aefd Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Sat, 11 Apr 2026 21:15:22 +0530
Subject: [PATCH] [Clang][Sema] Support block pointers as non-type template
parameters
Block pointer types were explicitly rejected as NTTPs with a diagnostic.
Since block pointers are scalar types, they satisfy the C++20 structural
type requirements and should be permitted as NTTPs, consistent with how
regular pointer types are handled.
This patch:
- Removes the explicit block pointer rejection in
CheckNonTypeTemplateParameterType and adds block pointers to the
allowed scalar type list, guarded by C++20 mode.
- Updates CheckTemplateArgument to handle BlockExpr as a valid LValue
base for block pointer template arguments.
- Updates CheckTemplateArgumentAddressOfObjectOrFunction and
CheckTemplateArgumentIsCompatibleWithParameter to handle block
pointer types alongside regular pointer types.
- Performs DefaultLvalueConversion in BuildExpressionFromDeclTemplateArgument
for block pointers to ensure we evaluate to an RValue, while
skipping address-of since the expression already has the correct type.
- Add block pointer support to Itanium mangling for LValue template
arguments by routing BlockExpr through mangleUnqualifiedBlock.
- Handle Expr-based LValue bases in ODRHash::AddStructuralValue to
avoid assertion failures when hashing block pointer template args.
- Add qualification conversion support for block pointers during
template argument deduction.
---
clang/lib/AST/ItaniumMangle.cpp | 12 ++++--
clang/lib/AST/ODRHash.cpp | 9 +++-
clang/lib/Sema/SemaTemplate.cpp | 33 +++++++++------
clang/lib/Sema/SemaTemplateDeduction.cpp | 1 +
clang/test/SemaCXX/nttp-blockpointer.cpp | 53 ++++++++++++++++++++++++
5 files changed, 89 insertions(+), 19 deletions(-)
create mode 100644 clang/test/SemaCXX/nttp-blockpointer.cpp
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index f58faa03bfa8c..af2b1b6ae0f20 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5009,7 +5009,11 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
}
// FIXME: invent manglings for all these.
- case Expr::BlockExprClass:
+ case Expr::BlockExprClass: {
+ NotPrimaryExpr();
+ mangleUnqualifiedBlock(cast<BlockExpr>(E)->getBlockDecl());
+ break;
+ }
case Expr::ChooseExprClass:
case Expr::CompoundLiteralExprClass:
case Expr::ExtVectorElementExprClass:
@@ -6775,7 +6779,7 @@ void CXXNameMangler::mangleValueInTemplateArg(QualType T, const APValue &V,
case APValue::LValue: {
// Proposed in https://github.com/itanium-cxx-abi/cxx-abi/issues/47.
- assert((T->isPointerOrReferenceType()) &&
+ assert((T->isPointerOrReferenceType() || T->isBlockPointerType()) &&
"unexpected type for LValue template arg");
if (V.isNullPointer()) {
@@ -6844,7 +6848,7 @@ void CXXNameMangler::mangleValueInTemplateArg(QualType T, const APValue &V,
Out << "cv";
mangleType(T);
}
- if (T->isPointerType())
+ if (T->isPointerType() || T->isBlockPointerType())
Out << "ad";
Out << "so";
mangleType(T->isVoidPointerType()
@@ -6859,7 +6863,7 @@ void CXXNameMangler::mangleValueInTemplateArg(QualType T, const APValue &V,
Out << "cv";
mangleType(T);
}
- if (T->isPointerType()) {
+ if (T->isPointerType() || T->isBlockPointerType()) {
NotPrimaryExpr();
Out << "ad";
}
diff --git a/clang/lib/AST/ODRHash.cpp b/clang/lib/AST/ODRHash.cpp
index 46a4e256ea3e5..0f883b240f30c 100644
--- a/clang/lib/AST/ODRHash.cpp
+++ b/clang/lib/AST/ODRHash.cpp
@@ -1270,8 +1270,13 @@ void ODRHash::AddStructuralValue(const APValue &Value) {
break;
}
- assert(Base.is<const ValueDecl *>());
- AddDecl(Base.get<const ValueDecl *>());
+ if (auto *VD = Base.dyn_cast<const ValueDecl *>()) {
+ AddDecl(VD);
+ } else if (auto *E = Base.dyn_cast<const Expr *>()) {
+ AddStmt(E);
+ } else {
+ llvm_unreachable("unexpected LValue base kind in structural value");
+ }
ID.AddInteger(Value.getLValueOffset().getQuantity());
bool OnePastTheEnd = Value.isLValueOnePastTheEnd();
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index c436b7018a2bd..2851c68c62c7e 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -1459,11 +1459,6 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
return QualType();
}
- if (T->isBlockPointerType()) {
- Diag(Loc, diag::err_template_nontype_parm_bad_type) << T;
- return QualType();
- }
-
// C++ [temp.param]p4:
//
// A non-type template-parameter shall have one of the following
@@ -1473,6 +1468,8 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
if (T->isIntegralOrEnumerationType() ||
// -- pointer to object or pointer to function,
T->isPointerType() ||
+ // -- block pointer type,
+ (getLangOpts().CPlusPlus20 && T->isBlockPointerType()) ||
// -- lvalue reference to object or lvalue reference to function,
T->isLValueReferenceType() ||
// -- pointer to member,
@@ -6670,8 +6667,8 @@ CheckTemplateArgumentIsCompatibleWithParameter(Sema &S, NamedDecl *Param,
QualType ParamType, Expr *ArgIn,
Expr *Arg, QualType ArgType) {
bool ObjCLifetimeConversion;
- if (ParamType->isPointerType() &&
- !ParamType->castAs<PointerType>()->getPointeeType()->isFunctionType() &&
+ if ((ParamType->isPointerType() || ParamType->isBlockPointerType()) &&
+ !ParamType->getPointeeType()->isFunctionType() &&
S.IsQualificationConversion(ArgType, ParamType, false,
ObjCLifetimeConversion)) {
// For pointer-to-object types, qualification conversions are
@@ -6821,7 +6818,8 @@ static bool CheckTemplateArgumentAddressOfObjectOrFunction(
Entity = CUE->getGuidDecl();
// If our parameter has pointer type, check for a null template value.
- if (ParamType->isPointerType() || ParamType->isNullPtrType()) {
+ if (ParamType->isPointerType() || ParamType->isBlockPointerType() ||
+ ParamType->isNullPtrType()) {
switch (isNullPointerValueTemplateArgument(S, Param, ParamType, ArgIn,
Entity)) {
case NPV_NullPointer:
@@ -7355,17 +7353,19 @@ ExprResult Sema::CheckTemplateArgument(NamedDecl *Param, QualType ParamType,
if (Value.isLValue()) {
APValue::LValueBase Base = Value.getLValueBase();
auto *VD = const_cast<ValueDecl *>(Base.dyn_cast<const ValueDecl *>());
+ auto *E = const_cast<Expr *>(Base.dyn_cast<const Expr *>());
// For a non-type template-parameter of pointer or reference type,
// the value of the constant expression shall not refer to
assert(ParamType->isPointerOrReferenceType() ||
- ParamType->isNullPtrType());
+ ParamType->isBlockPointerType() || ParamType->isNullPtrType());
// -- a temporary object
// -- a string literal
// -- the result of a typeid expression, or
// -- a predefined __func__ variable
if (Base &&
(!VD ||
- isa<LifetimeExtendedTemporaryDecl, UnnamedGlobalConstantDecl>(VD))) {
+ isa<LifetimeExtendedTemporaryDecl, UnnamedGlobalConstantDecl>(VD)) &&
+ (!E || !isa<BlockExpr>(E))) {
Diag(Arg->getBeginLoc(), diag::err_template_arg_not_decl_ref)
<< Arg->getSourceRange();
return ExprError();
@@ -7663,13 +7663,16 @@ ExprResult Sema::CheckTemplateArgument(NamedDecl *Param, QualType ParamType,
return Arg;
}
- if (ParamType->isPointerType()) {
+ if (ParamType->isPointerType() || ParamType->isBlockPointerType()) {
// -- for a non-type template-parameter of type pointer to
// object, qualification conversions (4.4) and the
// array-to-pointer conversion (4.2) are applied.
// C++0x also allows a value of std::nullptr_t.
- assert(ParamType->getPointeeType()->isIncompleteOrObjectType() &&
- "Only object pointers allowed here");
+ if (ParamType->isPointerType())
+ assert(ParamType->getPointeeType()->isIncompleteOrObjectType() &&
+ "Only object pointers allowed here");
+ else
+ assert(ParamType->isBlockPointerType() && "Expected block pointer");
if (CheckTemplateArgumentAddressOfObjectOrFunction(
*this, Param, ParamType, Arg, SugaredConverted, CanonicalConverted))
@@ -7976,6 +7979,10 @@ ExprResult Sema::BuildExpressionFromDeclTemplateArgument(
RefExpr = DefaultFunctionArrayConversion(RefExpr.get());
if (RefExpr.isInvalid())
return ExprError();
+ } else if (ParamType->isBlockPointerType()) {
+ RefExpr = DefaultLvalueConversion(RefExpr.get());
+ if (RefExpr.isInvalid())
+ return ExprError();
} else if (ParamType->isPointerType() || ParamType->isMemberPointerType()) {
// For any other pointer, take the address (or form a pointer-to-member).
RefExpr = CreateBuiltinUnaryOp(Loc, UO_AddrOf, RefExpr.get());
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index c71c40526ccdc..66bbc7b1353e1 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4986,6 +4986,7 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
// both P and A are pointers or member pointers. In this case, we
// just ignore cv-qualifiers completely).
if ((P->isPointerType() && A->isPointerType()) ||
+ (P->isBlockPointerType() && A->isBlockPointerType()) ||
(P->isMemberPointerType() && A->isMemberPointerType()))
TDF |= TDF_IgnoreQualifiers;
diff --git a/clang/test/SemaCXX/nttp-blockpointer.cpp b/clang/test/SemaCXX/nttp-blockpointer.cpp
new file mode 100644
index 0000000000000..eb91f87e5bd5a
--- /dev/null
+++ b/clang/test/SemaCXX/nttp-blockpointer.cpp
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -std=c++20 -fblocks -triple x86_64-apple-darwin %s -verify
+
+template<void (^B)()>
+struct A {
+ void call() { B(); }
+};
+
+constexpr void (^global_block)() = ^{};
+void test_global() {
+ A<global_block> a;
+ a.call();
+}
+
+void test_literal() {
+ A<^{}> a;
+ a.call();
+}
+
+void test_null() {
+ A<nullptr> a;
+}
+
+void test_capturing(int x) {
+ A<^{ (void)x; }> a; // expected-error {{non-type template argument is not a constant expression}}
+}
+
+template<void (^B)()>
+void deduce(A<B> a) {}
+
+void test_deduce() {
+ A<global_block> a;
+ deduce(a);
+}
+
+constexpr void (^another_block)() = ^{};
+static_assert(!__is_same(A<global_block>, A<another_block>));
+
+template<int (^B)(int)>
+struct BFunc {
+ int call(int x) { return B(x); }
+};
+
+void test_params() {
+ BFunc<^(int x) { return x + 1; }> b;
+ (void)b.call(1);
+}
+
+template<auto B>
+struct AutoBlock {};
+
+void test_auto() {
+ AutoBlock<global_block> a;
+}
More information about the cfe-commits
mailing list