[clang] [clang][Sema] support block pointers as non-type template parameters (PR #191694)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Apr 17 05:12:27 PDT 2026
https://github.com/Serosh-commits updated https://github.com/llvm/llvm-project/pull/191694
>From 7d5b73ca2a379c5240d13137d52ac6d98985b05f 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 1/7] [Clang][Sema] Support block pointers as non-type template
parameters
- 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.
Fixes #189247
---
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;
+}
>From 1551be3f055cad4b56f5ab16d828fe99b67726b0 Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Tue, 14 Apr 2026 00:13:57 +0530
Subject: [PATCH 2/7] Address feedback and fixme update
---
clang/lib/AST/ItaniumMangle.cpp | 3 ++-
clang/lib/Sema/SemaTemplate.cpp | 2 +-
clang/test/SemaCXX/blocks.cpp | 2 +-
clang/test/SemaCXX/nttp-blockpointer.cpp | 1 +
4 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index af2b1b6ae0f20..93fc1f64481f3 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5008,12 +5008,13 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
break;
}
- // FIXME: invent manglings for all these.
case Expr::BlockExprClass: {
NotPrimaryExpr();
mangleUnqualifiedBlock(cast<BlockExpr>(E)->getBlockDecl());
break;
}
+
+ // FIXME: invent manglings for all these.
case Expr::ChooseExprClass:
case Expr::CompoundLiteralExprClass:
case Expr::ExtVectorElementExprClass:
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 2851c68c62c7e..f584d6a135a81 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -1469,7 +1469,7 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
// -- pointer to object or pointer to function,
T->isPointerType() ||
// -- block pointer type,
- (getLangOpts().CPlusPlus20 && T->isBlockPointerType()) ||
+ T->isBlockPointerType() ||
// -- lvalue reference to object or lvalue reference to function,
T->isLValueReferenceType() ||
// -- pointer to member,
diff --git a/clang/test/SemaCXX/blocks.cpp b/clang/test/SemaCXX/blocks.cpp
index 67ac7d42f52c9..557482c9a390e 100644
--- a/clang/test/SemaCXX/blocks.cpp
+++ b/clang/test/SemaCXX/blocks.cpp
@@ -165,5 +165,5 @@ void static_data_member() {
}
namespace gh189247 {
- template<void (^)()> struct A; // expected-error {{a non-type template parameter cannot have type 'void (^)()'}}
+ template<void (^)()> struct A;
}
diff --git a/clang/test/SemaCXX/nttp-blockpointer.cpp b/clang/test/SemaCXX/nttp-blockpointer.cpp
index eb91f87e5bd5a..f5f604332d0ad 100644
--- a/clang/test/SemaCXX/nttp-blockpointer.cpp
+++ b/clang/test/SemaCXX/nttp-blockpointer.cpp
@@ -45,6 +45,7 @@ void test_params() {
(void)b.call(1);
}
+
template<auto B>
struct AutoBlock {};
>From 61bb53d7658eeafa340dc881a54a2fc22cb111cc Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Tue, 14 Apr 2026 00:53:08 +0530
Subject: [PATCH 3/7] add codegen tests
---
clang/test/CodeGenCXX/nttp-blockpointer.cpp | 26 +++++++++++++++++++++
1 file changed, 26 insertions(+)
create mode 100644 clang/test/CodeGenCXX/nttp-blockpointer.cpp
diff --git a/clang/test/CodeGenCXX/nttp-blockpointer.cpp b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
new file mode 100644
index 0000000000000..546148623208e
--- /dev/null
+++ b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -std=c++20 -fblocks -triple x86_64-apple-darwin -emit-llvm -o - %s | FileCheck %s
+
+template<void (^B)()> void f() {}
+
+// CHECK: define{{.*}} void @_Z12test_literalv()
+void test_literal() {
+ // CHECK: call void @_Z1fIXcvU13block_pointerFvvEadUb_EEvv()
+ f<^{}>();
+}
+
+constexpr void (^global_block)() = ^{};
+// CHECK: define{{.*}} void @_Z11test_globalv()
+void test_global() {
+ // CHECK: call void @_Z1fIXcvU13block_pointerFvvEadUb0_EEvv()
+ f<global_block>();
+}
+
+template<int (^B)(int)> struct S {
+ static int call(int x) { return B(x); }
+};
+
+// CHECK: define{{.*}} i32 @_Z10test_parami(i32 noundef %x)
+int test_param(int x) {
+ // CHECK: call noundef i32 @_ZN1SIXadUb1_EE4callEi(i32 noundef %0)
+ return S<^(int x) { return x + 1; }>::call(x);
+}
>From 20d64884be83bc2cfe80d5146e535babba9f2e52 Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Wed, 15 Apr 2026 02:06:54 +0530
Subject: [PATCH 4/7] update test
---
clang/test/CodeGenCXX/nttp-blockpointer.cpp | 3 ---
1 file changed, 3 deletions(-)
diff --git a/clang/test/CodeGenCXX/nttp-blockpointer.cpp b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
index 546148623208e..7f979bf7b274d 100644
--- a/clang/test/CodeGenCXX/nttp-blockpointer.cpp
+++ b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
@@ -2,14 +2,12 @@
template<void (^B)()> void f() {}
-// CHECK: define{{.*}} void @_Z12test_literalv()
void test_literal() {
// CHECK: call void @_Z1fIXcvU13block_pointerFvvEadUb_EEvv()
f<^{}>();
}
constexpr void (^global_block)() = ^{};
-// CHECK: define{{.*}} void @_Z11test_globalv()
void test_global() {
// CHECK: call void @_Z1fIXcvU13block_pointerFvvEadUb0_EEvv()
f<global_block>();
@@ -19,7 +17,6 @@ template<int (^B)(int)> struct S {
static int call(int x) { return B(x); }
};
-// CHECK: define{{.*}} i32 @_Z10test_parami(i32 noundef %x)
int test_param(int x) {
// CHECK: call noundef i32 @_ZN1SIXadUb1_EE4callEi(i32 noundef %0)
return S<^(int x) { return x + 1; }>::call(x);
>From 2f54b8d92ddc527f1115b95aa7c95d04e183902b Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Thu, 16 Apr 2026 01:22:36 +0530
Subject: [PATCH 5/7] update test
---
clang/docs/ReleaseNotes.rst | 2 +
clang/test/CodeGenCXX/nttp-blockpointer.cpp | 47 +++++++++++++++++++++
2 files changed, 49 insertions(+)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index fd58d7847717c..4ee8d20e5e99b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -122,6 +122,8 @@ What's New in Clang |release|?
C++ Language Changes
--------------------
+- Clang now supports block pointers as non-type template parameters. (#GH189247)
+
- ``__is_trivially_equality_comparable`` no longer returns false for all enum types. (#GH132672)
C++2c Feature Support
diff --git a/clang/test/CodeGenCXX/nttp-blockpointer.cpp b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
index 7f979bf7b274d..ee6f752a227d8 100644
--- a/clang/test/CodeGenCXX/nttp-blockpointer.cpp
+++ b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
@@ -21,3 +21,50 @@ int test_param(int x) {
// CHECK: call noundef i32 @_ZN1SIXadUb1_EE4callEi(i32 noundef %0)
return S<^(int x) { return x + 1; }>::call(x);
}
+
+void test_nullptr() {
+ // CHECK: call void @_Z1fILU13block_pointerFvvE0EEvv()
+ f<nullptr>();
+}
+
+namespace TestNamespace {
+ template<void (^B)()> struct S {
+ static void call() { B(); }
+ };
+ void test_namespace() {
+ // CHECK: call void @_ZN13TestNamespace1SIXadUb2_EE4callEv()
+ S<^{}>::call();
+ }
+}
+
+template<void (^B)() = ^{}>
+void f_default() { B(); }
+
+void test_default() {
+ // CHECK: call void @_Z9f_defaultIXcvU13block_pointerFvvEadUb_EEvv()
+ f_default();
+}
+
+struct Structural {
+ void (^b)();
+};
+
+template<Structural s>
+void f_struct() {
+ s.b();
+}
+
+void test_struct() {
+ // CHECK: call void @_Z8f_structIXtl10StructuraladUb3_EEEvv()
+ f_struct<Structural{^{}}>();
+}
+
+template<void (^...Blocks)()>
+void f_variadic() {
+ (Blocks(), ...);
+}
+
+void test_variadic() {
+ // CHECK: call void @_Z10f_variadicIJXcvU13block_pointerFvvEadUb4_EXcvS1_adUb5_EEEvv()
+ f_variadic<^{}, ^{}>();
+}
>From a076a625a83f7b8b9aaf2e0eb5826f9bac2910e5 Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Thu, 16 Apr 2026 02:58:07 +0530
Subject: [PATCH 6/7] address feedback
---
clang/lib/Sema/SemaTemplate.cpp | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index f584d6a135a81..98910f8a7d254 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -1468,8 +1468,6 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
if (T->isIntegralOrEnumerationType() ||
// -- pointer to object or pointer to function,
T->isPointerType() ||
- // -- block pointer type,
- T->isBlockPointerType() ||
// -- lvalue reference to object or lvalue reference to function,
T->isLValueReferenceType() ||
// -- pointer to member,
@@ -1483,6 +1481,10 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
return T.getUnqualifiedType();
}
+ // Allow block pointer types as an extension.
+ if (T->isBlockPointerType())
+ return T.getUnqualifiedType();
+
// C++ [temp.param]p8:
//
// A non-type template-parameter of type "array of T" or
>From 1ca502924fccc077c1ce98c47e894c4005299563 Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Fri, 17 Apr 2026 17:40:20 +0530
Subject: [PATCH 7/7] address feedback
---
clang/test/CodeGenCXX/nttp-blockpointer.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/clang/test/CodeGenCXX/nttp-blockpointer.cpp b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
index ee6f752a227d8..1a4028a239647 100644
--- a/clang/test/CodeGenCXX/nttp-blockpointer.cpp
+++ b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
@@ -68,3 +68,5 @@ void test_variadic() {
// CHECK: call void @_Z10f_variadicIJXcvU13block_pointerFvvEadUb4_EXcvS1_adUb5_EEEvv()
f_variadic<^{}, ^{}>();
}
+
+// CHECK: define internal void @_Z1fIXcvU13block_pointerFvvEadUb_EEvv()
More information about the cfe-commits
mailing list