[llvm-branch-commits] [clang] [Clang] [C++26] Expansion Statements (Part 7: Constexpr support and tests) (PR #169686)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Nov 26 10:05:10 PST 2025
https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/169686
>From f1a507f070468fb96513bbc937d824bd3cac290a Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 26 Nov 2025 17:09:56 +0100
Subject: [PATCH] [Clang] [C++26] Expansion Statements (Part 7)
---
clang/lib/AST/ExprConstant.cpp | 40 +
clang/lib/Sema/SemaDeclCXX.cpp | 3 +
clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 1042 ++++++++++++++++++
3 files changed, 1085 insertions(+)
create mode 100644 clang/test/SemaCXX/cxx2c-expansion-stmts.cpp
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d93f87a27e68d..2de1641f6b46a 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -6037,6 +6037,12 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
const VarDecl *VD = dyn_cast_or_null<VarDecl>(D);
if (VD && !CheckLocalVariableDeclaration(Info, VD))
return ESR_Failed;
+
+ if (const auto *ESD = dyn_cast<CXXExpansionStmtDecl>(D)) {
+ assert(ESD->getInstantiations() && "not expanded?");
+ return EvaluateStmt(Result, Info, ESD->getInstantiations(), Case);
+ }
+
// Each declaration initialization is its own full-expression.
FullExpressionRAII Scope(Info);
if (!EvaluateDecl(Info, D, /*EvaluateConditionDecl=*/true) &&
@@ -6309,6 +6315,40 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
return Scope.destroy() ? ESR_Succeeded : ESR_Failed;
}
+ case Stmt::CXXExpansionStmtInstantiationClass: {
+ BlockScopeRAII Scope(Info);
+ const auto *Expansion = cast<CXXExpansionStmtInstantiation>(S);
+ for (const Stmt *Shared : Expansion->getSharedStmts()) {
+ EvalStmtResult ESR = EvaluateStmt(Result, Info, Shared);
+ if (ESR != ESR_Succeeded) {
+ if (ESR != ESR_Failed && !Scope.destroy())
+ return ESR_Failed;
+ return ESR;
+ }
+ }
+
+ // No need to push an extra scope for these since they're already
+ // CompoundStmts.
+ EvalStmtResult ESR = ESR_Succeeded;
+ for (const Stmt *Instantiation : Expansion->getInstantiations()) {
+ ESR = EvaluateStmt(Result, Info, Instantiation);
+ if (ESR == ESR_Failed ||
+ ShouldPropagateBreakContinue(Info, Expansion, &Scope, ESR))
+ return ESR;
+ if (ESR != ESR_Continue) {
+ // Succeeded here actually means we encountered a 'break'.
+ assert(ESR == ESR_Succeeded || ESR == ESR_Returned);
+ break;
+ }
+ }
+
+ // Map Continue back to Succeeded if we fell off the end of the loop.
+ if (ESR == ESR_Continue)
+ ESR = ESR_Succeeded;
+
+ return Scope.destroy() ? ESR : ESR_Failed;
+ }
+
case Stmt::SwitchStmtClass:
return EvaluateSwitch(Result, Info, cast<SwitchStmt>(S));
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 8030aac3d8771..e398710ace63b 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -2027,6 +2027,9 @@ static bool CheckConstexprDeclStmt(Sema &SemaRef, const FunctionDecl *Dcl,
// - using-enum-declaration
continue;
+ case Decl::CXXExpansionStmt:
+ continue;
+
case Decl::Typedef:
case Decl::TypeAlias: {
// - typedef declarations and alias-declarations that do not define
diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp
new file mode 100644
index 0000000000000..71ce71c4f69fe
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp
@@ -0,0 +1,1042 @@
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -verify
+namespace std {
+template <typename T>
+struct initializer_list {
+ const T* a;
+ const T* b;
+ initializer_list(T* a, T* b): a{a}, b{b} {}
+};
+}
+
+struct S {
+ int x;
+ constexpr S(int x) : x{x} {}
+};
+
+void g(int); // #g
+template <int n> constexpr int tg() { return n; }
+
+void f1() {
+ template for (auto x : {}) static_assert(false, "discarded");
+ template for (constexpr auto x : {}) static_assert(false, "discarded");
+ template for (auto x : {1}) g(x);
+ template for (auto x : {1, 2, 3}) g(x);
+ template for (constexpr auto x : {1}) g(x);
+ template for (constexpr auto x : {1, 2, 3}) g(x);
+ template for (constexpr auto x : {1}) tg<x>();
+ template for (constexpr auto x : {1, 2, 3})
+ static_assert(tg<x>());
+
+ template for (int x : {1, 2, 3}) g(x);
+ template for (S x : {1, 2, 3}) g(x.x);
+ template for (constexpr S x : {1, 2, 3}) tg<x.x>();
+
+ template for (int x : {"1", S(1), {1, 2}}) { // expected-error {{cannot initialize a variable of type 'int' with an lvalue of type 'const char[2]'}} \
+ expected-error {{no viable conversion from 'S' to 'int'}} \
+ expected-error {{excess elements in scalar initializer}} \
+ expected-note 3 {{in instantiation of expansion statement requested here}}
+ g(x);
+ }
+
+ template for (constexpr auto x : {1, 2, 3, 4}) { // expected-note 3 {{in instantiation of expansion statement requested here}}
+ static_assert(tg<x>() == 4); // expected-error 3 {{static assertion failed due to requirement 'tg<x>() == 4'}} \
+ expected-note {{expression evaluates to '1 == 4'}} \
+ expected-note {{expression evaluates to '2 == 4'}} \
+ expected-note {{expression evaluates to '3 == 4'}}
+ }
+
+
+ template for (constexpr auto x : {1, 2}) { // expected-note 2 {{in instantiation of expansion statement requested here}}
+ static_assert(false, "not discarded"); // expected-error 2 {{static assertion failed: not discarded}}
+ }
+}
+
+template <typename T>
+void t1() {
+ template for (T x : {}) g(x);
+ template for (constexpr T x : {}) g(x);
+ template for (auto x : {}) g(x);
+ template for (constexpr auto x : {}) g(x);
+ template for (T x : {1, 2}) g(x);
+ template for (T x : {T(1), T(2)}) g(x);
+ template for (auto x : {T(1), T(2)}) g(x);
+ template for (constexpr T x : {T(1), T(2)}) static_assert(tg<x>());
+ template for (constexpr auto x : {T(1), T(2)}) static_assert(tg<x>());
+}
+
+template <typename U>
+struct s1 {
+ template <typename T>
+ void tf() {
+ template for (T x : {}) g(x);
+ template for (constexpr T x : {}) g(x);
+ template for (U x : {}) g(x);
+ template for (constexpr U x : {}) g(x);
+ template for (auto x : {}) g(x);
+ template for (constexpr auto x : {}) g(x);
+ template for (T x : {1, 2}) g(x);
+ template for (U x : {1, 2}) g(x);
+ template for (U x : {T(1), T(2)}) g(x);
+ template for (T x : {U(1), U(2)}) g(x);
+ template for (auto x : {T(1), T(2)}) g(x);
+ template for (auto x : {U(1), T(2)}) g(x);
+ template for (constexpr U x : {T(1), T(2)}) static_assert(tg<x>());
+ template for (constexpr T x : {U(1), U(2)}) static_assert(tg<x>());
+ template for (constexpr auto x : {T(1), U(2)}) static_assert(tg<x>());
+ }
+};
+
+template <typename T>
+void t2() {
+ template for (T x : {}) g(x);
+}
+
+void f2() {
+ t1<int>();
+ t1<long>();
+ s1<long>().tf<long>();
+ s1<int>().tf<int>();
+ s1<int>().tf<long>();
+ s1<long>().tf<int>();
+ t2<S>();
+ t2<S[1231]>();
+ t2<S***>();
+}
+
+template <__SIZE_TYPE__ size>
+struct String {
+ char data[size];
+
+ template <__SIZE_TYPE__ n>
+ constexpr String(const char (&str)[n]) { __builtin_memcpy(data, str, n); }
+
+ constexpr const char* begin() const { return data; }
+ constexpr const char* end() const { return data + size - 1; }
+};
+
+template <__SIZE_TYPE__ n>
+String(const char (&str)[n]) -> String<n>;
+
+constexpr int f3() {
+ static constexpr String s{"abcd"};
+ int count = 0;
+ template for (constexpr auto x : s) count++;
+ return count;
+}
+
+template <String s>
+constexpr int tf3() {
+ int count = 0;
+ template for (constexpr auto x : s) count++;
+ return count;
+}
+
+static_assert(f3() == 4);
+static_assert(tf3<"1">() == 1);
+static_assert(tf3<"12">() == 2);
+static_assert(tf3<"123">() == 3);
+static_assert(tf3<"1234">() == 4);
+
+void f4() {
+ static constexpr String empty{""};
+ static constexpr String s{"abcd"};
+ template for (auto x : empty) static_assert(false, "not expanded");
+ template for (constexpr auto x : s) g(x);
+ template for (auto x : s) g(x);
+}
+
+struct NegativeSize {
+ static constexpr const char* str = "123";
+ constexpr const char* begin() const { return str + 3; }
+ constexpr const char* end() const { return str; }
+};
+
+void negative_size() {
+ static constexpr NegativeSize n;
+ template for (auto x : n) g(x); // expected-error {{expansion size must not be negative (was -3)}}
+ template for (constexpr auto x : n) g(x); // expected-error {{expansion size must not be negative (was -3)}}
+}
+
+template <typename T, __SIZE_TYPE__ size>
+struct Array {
+ T data[size]{};
+ constexpr const T* begin() const { return data; }
+ constexpr const T* end() const { return data + size; }
+};
+
+struct NotInt {
+ struct iterator {};
+ constexpr iterator begin() const { return {}; }
+ constexpr iterator end() const { return {}; }
+};
+
+void not_int() {
+ static constexpr NotInt ni;
+ template for (auto x : ni) g(x); // expected-error {{invalid operands to binary expression}}
+}
+
+static constexpr Array<int, 3> integers{1, 2, 3};
+
+constexpr int friend_func();
+
+struct Private {
+ friend constexpr int friend_func();
+
+private:
+ constexpr const int* begin() const { return integers.begin(); } // expected-note 2 {{declared private here}}
+ constexpr const int* end() const { return integers.end(); } // expected-note 2 {{declared private here}}
+
+public:
+ static constexpr int member_func() {
+ int sum = 0;
+ static constexpr Private p1;
+ template for (auto x : p1) sum += x;
+ return sum;
+ }
+};
+
+struct Protected {
+ friend constexpr int friend_func();
+
+protected:
+ constexpr const int* begin() const { return integers.begin(); } // expected-note 2 {{declared protected here}}
+ constexpr const int* end() const { return integers.end(); } // expected-note 2 {{declared protected here}}
+
+public:
+ static constexpr int member_func() {
+ int sum = 0;
+ static constexpr Protected p1;
+ template for (auto x : p1) sum += x;
+ return sum;
+ }
+};
+
+void access_control() {
+ static constexpr Private p1;
+ template for (auto x : p1) g(x); // expected-error 2 {{'begin' is a private member of 'Private'}} expected-error 2 {{'end' is a private member of 'Private'}}
+
+ static constexpr Protected p2;
+ template for (auto x : p2) g(x); // expected-error 2 {{'begin' is a protected member of 'Protected'}} expected-error 2 {{'end' is a protected member of 'Protected'}}
+}
+
+constexpr int friend_func() {
+ int sum = 0;
+ static constexpr Private p1;
+ template for (auto x : p1) sum += x;
+
+ static constexpr Protected p2;
+ template for (auto x : p2) sum += x;
+ return sum;
+}
+
+static_assert(friend_func() == 12);
+static_assert(Private::member_func() == 6);
+static_assert(Protected::member_func() == 6);
+
+struct SizeNotICE {
+ struct iterator {
+ friend constexpr iterator operator+(iterator a, __PTRDIFF_TYPE__) { return a; }
+ int constexpr operator*() const { return 7; }
+
+ // NOT constexpr!
+ friend int operator-(iterator, iterator) { return 7; } // expected-note {{declared here}}
+ friend int operator!=(iterator, iterator) { return 7; }
+ };
+ constexpr iterator begin() const { return {}; }
+ constexpr iterator end() const { return {}; }
+};
+
+struct PlusMissing {
+ struct iterator {
+ int constexpr operator*() const { return 7; }
+ };
+ constexpr iterator begin() const { return {}; }
+ constexpr iterator end() const { return {}; }
+};
+
+struct DerefMissing {
+ struct iterator {
+ friend constexpr iterator operator+(iterator a, __PTRDIFF_TYPE__) { return a; }
+ };
+ constexpr iterator begin() const { return {}; }
+ constexpr iterator end() const { return {}; }
+};
+
+void missing_funcs() {
+ static constexpr SizeNotICE s1;
+ static constexpr PlusMissing s2;
+ static constexpr DerefMissing s3;
+
+ // TODO: This message should start complaining about '!=' once we support the
+ // proper way of computing the size.
+ template for (auto x : s1) g(x); // expected-error {{expansion size is not a constant expression}} \
+ expected-note {{non-constexpr function 'operator-' cannot be used in a constant expression}}
+
+ template for (auto x : s2) g(x); // expected-error {{invalid operands to binary expression}}
+ template for (auto x : s3) g(x); // expected-error {{indirection requires pointer operand ('iterator' invalid)}}
+}
+
+namespace adl {
+struct ADL {
+
+};
+
+constexpr const int* begin(const ADL&) { return integers.begin(); }
+constexpr const int* end(const ADL&) { return integers.end(); }
+}
+
+namespace adl_error {
+struct ADLError1 {
+ constexpr const int* begin() const { return integers.begin(); }
+};
+
+struct ADLError2 {
+ constexpr const int* end() const { return integers.end(); }
+};
+
+constexpr const int* begin(const ADLError2&) { return integers.begin(); }
+constexpr const int* end(const ADLError1&) { return integers.end(); }
+}
+
+namespace adl_both {
+static constexpr Array<int, 5> integers2{1, 2, 3, 4, 5};
+struct ADLBoth {
+ // Test that member begin/end are preferred over ADl begin/end. These return
+ // pointers to a different array.
+ constexpr const int* begin() const { return integers2.begin(); }
+ constexpr const int* end() const { return integers2.end(); }
+};
+
+constexpr const int* begin(const ADLBoth&) { return integers.begin(); }
+constexpr const int* end(const ADLBoth&) { return integers.end(); }
+}
+
+constexpr int adl_begin_end() {
+ static constexpr adl::ADL a;
+ int sum = 0;
+ template for (auto x : a) sum += x;
+ template for (constexpr auto x : a) sum += x;
+ return sum;
+}
+
+static_assert(adl_begin_end() == 12);
+
+void adl_mixed() {
+ static constexpr adl_error::ADLError1 a1;
+ static constexpr adl_error::ADLError2 a2;
+
+ // These are actually destructuring because there is no
+ // valid begin/end pair.
+ template for (auto x : a1) g(x);
+ template for (auto x : a2) g(x);
+}
+
+constexpr int adl_both_test() {
+ static constexpr adl_both::ADLBoth a;
+ int sum = 0;
+ template for (auto x : a) sum += x;
+ return sum;
+}
+
+static_assert(adl_both_test() == 15);
+
+struct A {};
+struct B { int x = 1; };
+struct C { int a = 1, b = 2, c = 3; };
+struct D {
+ int a = 1;
+ int* b = nullptr;
+ const char* c = "3";
+};
+
+struct Nested {
+ A a;
+ B b;
+ C c;
+};
+
+struct PrivateDestructurable {
+ friend void destructurable_friend();
+private:
+ int a, b; // expected-note 4 {{declared private here}}
+};
+
+struct ProtectedDestructurable {
+ friend void destructurable_friend();
+protected:
+ int a, b; // expected-note 4 {{declared protected here}}
+};
+
+void destructuring() {
+ static constexpr A a;
+ static constexpr B b;
+ static constexpr C c;
+ static constexpr D d;
+
+ template for (auto x : a) static_assert(false, "not expanded");
+ template for (constexpr auto x : a) static_assert(false, "not expanded");
+
+ template for (auto x : b) g(x);
+ template for (constexpr auto x : b) g(x);
+
+ template for (auto x : c) g(x);
+ template for (constexpr auto x : c) g(x);
+
+ template for (auto x : d) { // expected-note 2 {{in instantiation of expansion statement requested here}}
+ // expected-note@#g {{candidate function not viable: no known conversion from 'int *' to 'int' for 1st argument}}
+ // expected-note@#g {{candidate function not viable: no known conversion from 'const char *' to 'int' for 1st argument}}
+ g(x); // expected-error 2 {{no matching function for call to 'g'}}
+
+ }
+
+ template for (constexpr auto x : d) { // expected-note 2 {{in instantiation of expansion statement requested here}}
+ // expected-note@#g {{candidate function not viable: no known conversion from 'int *const' to 'int' for 1st argument}}
+ // expected-note@#g {{candidate function not viable: no known conversion from 'const char *const' to 'int' for 1st argument}}
+ g(x); // expected-error 2 {{no matching function for call to 'g'}}
+ }
+}
+
+constexpr int array() {
+ static constexpr int x[4]{1, 2, 3, 4};
+ int sum = 0;
+ template for (auto y : x) sum += y;
+ template for (constexpr auto y : x) sum += y;
+ return sum;
+}
+
+static_assert(array() == 20);
+
+template <auto v>
+constexpr int destructure() {
+ int sum = 0;
+ template for (auto x : v) sum += x;
+ template for (constexpr auto x : v) sum += x;
+ return sum;
+}
+
+static_assert(destructure<B{10}>() == 20);
+static_assert(destructure<C{}>() == 12);
+static_assert(destructure<C{3, 4, 5}>() == 24);
+
+constexpr int nested() {
+ static constexpr Nested n;
+ int sum = 0;
+ template for (constexpr auto x : n) {
+ static constexpr auto val = x;
+ template for (auto y : val) {
+ sum += y;
+ }
+ }
+ template for (constexpr auto x : n) {
+ static constexpr auto val = x;
+ template for (constexpr auto y : val) {
+ sum += y;
+ }
+ }
+ return sum;
+}
+
+static_assert(nested() == 14);
+
+void access_control_destructurable() {
+ template for (auto x : PrivateDestructurable()) {} // expected-error 2 {{cannot bind private member 'a' of 'PrivateDestructurable'}} \
+ expected-error 2 {{cannot bind private member 'b' of 'PrivateDestructurable'}}
+
+ template for (auto x : ProtectedDestructurable()) {} // expected-error 2 {{cannot bind protected member 'a' of 'ProtectedDestructurable'}} \
+ expected-error 2 {{cannot bind protected member 'b' of 'ProtectedDestructurable'}}
+}
+
+void destructurable_friend() {
+ template for (auto x : PrivateDestructurable()) {}
+ template for (auto x : ProtectedDestructurable()) {}
+}
+
+struct Placeholder {
+ A get_value() const { return {}; }
+ __declspec(property(get = get_value)) A a;
+};
+
+void placeholder() {
+ template for (auto x: Placeholder().a) {}
+}
+
+union Union { int a; long b;};
+
+struct MemberPtr {
+ void f() {}
+};
+
+void overload_set(int); // expected-note 2 {{possible target for call}}
+void overload_set(long); // expected-note 2 {{possible target for call}}
+
+void invalid_types() {
+ template for (auto x : void()) {} // expected-error {{cannot expand expression of type 'void'}}
+ template for (auto x : 1) {} // expected-error {{cannot expand expression of type 'int'}}
+ template for (auto x : 1.f) {} // expected-error {{cannot expand expression of type 'float'}}
+ template for (auto x : 'c') {} // expected-error {{cannot expand expression of type 'char'}}
+ template for (auto x : invalid_types) {} // expected-error {{cannot expand expression of type 'void ()'}}
+ template for (auto x : &invalid_types) {} // expected-error {{cannot expand expression of type 'void (*)()'}}
+ template for (auto x : &MemberPtr::f) {} // expected-error {{cannot expand expression of type 'void (MemberPtr::*)()'}}
+ template for (auto x : overload_set) {} // expected-error{{reference to overloaded function could not be resolved; did you mean to call it?}}
+ template for (auto x : &overload_set) {} // expected-error{{reference to overloaded function could not be resolved; did you mean to call it?}}
+ template for (auto x : nullptr) {} // expected-error {{cannot expand expression of type 'std::nullptr_t'}}
+ template for (auto x : __builtin_strlen) {} // expected-error {{builtin functions must be directly called}}
+ template for (auto x : Union()) {} // expected-error {{cannot expand expression of type 'Union'}}
+ template for (auto x : (char*)nullptr) {} // expected-error {{cannot expand expression of type 'char *'}}
+ template for (auto x : []{}) {} // expected-error {{cannot expand lambda closure type}}
+ template for (auto x : [x=3]{}) {} // expected-error {{cannot expand lambda closure type}}
+}
+
+struct BeginOnly {
+ int x{1};
+ constexpr const int* begin() const { return nullptr; }
+};
+
+struct EndOnly {
+ int x{2};
+ constexpr const int* end() const { return nullptr; }
+};
+
+namespace adl1 {
+struct BeginOnly {
+ int x{3};
+};
+constexpr const int* begin(const BeginOnly&) { return nullptr; }
+}
+
+namespace adl2 {
+struct EndOnly {
+ int x{4};
+};
+constexpr const int* end(const EndOnly&) { return nullptr; }
+}
+
+namespace adl3 {
+struct BeginOnlyDeleted {
+ int x{4};
+};
+constexpr const int* begin(const BeginOnlyDeleted&) = delete;
+}
+
+namespace adl4 {
+struct EndOnlyDeleted {
+ int x{4};
+};
+constexpr const int* end(const EndOnlyDeleted&) = delete;
+}
+
+namespace adl5 {
+struct BothDeleted {
+ int x{4};
+};
+constexpr const int* begin(const BothDeleted&) = delete; // expected-note {{candidate function has been explicitly deleted}}
+constexpr const int* end(const BothDeleted&) = delete;
+}
+
+namespace adl6 {
+struct BeginNotViable {
+ int x{4};
+};
+constexpr const int* begin(int) { return nullptr; }
+}
+
+namespace adl7 {
+struct EndNotViable {
+ int x{4};
+};
+constexpr const int* end(int) { return nullptr; }
+}
+
+namespace adl8 {
+struct BothNotViable {
+ int x{4};
+};
+constexpr const int* begin(int) { return nullptr; }
+constexpr const int* end(int) { return nullptr; }
+}
+
+namespace adl9 {
+struct BeginDeleted {
+ int x{4};
+};
+constexpr const int* begin(const BeginDeleted&) = delete; // expected-note {{candidate function has been explicitly deleted}}
+constexpr const int* end(const BeginDeleted&) { return nullptr; }
+}
+
+namespace adl10 {
+struct EndDeleted {
+ int x{4};
+};
+constexpr const int* begin(const EndDeleted&) { return nullptr; }
+constexpr const int* end(const EndDeleted&) = delete; // expected-note {{candidate function has been explicitly deleted}}
+}
+
+void unpaired_begin_end() {
+ static constexpr adl1::BeginOnly begin_only;
+ static constexpr adl2::EndOnly end_only;
+ static constexpr adl3::BeginOnlyDeleted begin_only_deleted;
+ static constexpr adl4::EndOnlyDeleted end_only_deleted;
+ static constexpr adl5::BothDeleted both_deleted;
+ static constexpr adl6::BeginNotViable begin_not_viable;
+ static constexpr adl7::EndNotViable end_not_viable;
+ static constexpr adl8::BothNotViable both_not_viable;
+ static constexpr adl9::BeginDeleted begin_deleted;
+ static constexpr adl10::EndDeleted end_deleted;
+
+ // Ok, these are destructuring because there is no valid pair.
+ template for (auto x : begin_only) {}
+ template for (auto x : begin_only_deleted) {}
+ template for (auto x : begin_not_viable) {}
+ template for (auto x : end_only) {}
+ template for (auto x : end_only_deleted) {}
+ template for (auto x : end_not_viable) {}
+
+ // This is also ok because overload resolution fails.
+ template for (auto x : both_not_viable) {}
+
+ // These are invalid because overload resolution succeeds (even though
+ // there is no usable begin() and/or end()).
+ template for (auto x : both_deleted) {} // expected-error {{call to deleted function 'begin'}} \
+ expected-note {{when looking up 'begin' function for range expression of type 'const adl5::BothDeleted'}}~
+
+ template for (auto x : begin_deleted) {} // expected-error {{call to deleted function 'begin'}} \
+ expected-note {{when looking up 'begin' function for range expression of type 'const adl9::BeginDeleted'}}
+
+ template for (auto x : end_deleted) {} // expected-error {{call to deleted function 'end'}} \
+ expected-note {{when looking up 'end' function for range expression of type 'const adl10::EndDeleted'}}
+}
+
+// Examples taken from [stmt.expand].
+namespace stmt_expand_examples {
+consteval int f(auto const&... Containers) {
+ int result = 0;
+ template for (auto const& c : {Containers...}) { // OK, enumerating expansion statement
+ result += c[0];
+ }
+ return result;
+}
+constexpr int c1[] = {1, 2, 3};
+constexpr int c2[] = {4, 3, 2, 1};
+static_assert(f(c1, c2) == 5);
+
+// TODO: This entire example should work without issuing any diagnostics once
+// we have full support for references to constexpr variables (P2686).
+consteval int f() {
+ constexpr Array<int, 3> arr {1, 2, 3}; // expected-note{{add 'static' to give it a constant address}}
+
+ int result = 0;
+
+ // expected-error@#invalid-ref {{constexpr variable '__range1' must be initialized by a constant expression}}
+ // expected-error@#invalid-ref {{constexpr variable '__begin1' must be initialized by a constant expression}}
+ // expected-error@#invalid-ref {{constexpr variable '__end1' must be initialized by a constant expression}}
+ // expected-error@#invalid-ref {{expansion size is not a constant expression}}
+ // expected-note@#invalid-ref 2 {{member call on variable '__range1' whose value is not known}}
+ // expected-note@#invalid-ref 1 {{initializer of '__end1' is not a constant expression}}
+ // expected-note@#invalid-ref 3 {{declared here}}
+ // expected-note@#invalid-ref {{reference to 'arr' is not a constant expression}}
+ template for (constexpr int s : arr) { // #invalid-ref // OK, iterating expansion statement
+ result += sizeof(char[s]);
+ }
+ return result;
+}
+static_assert(f() == 6); // expected-error {{static assertion failed due to requirement 'f() == 6'}} expected-note {{expression evaluates to '0 == 6'}}
+
+struct S {
+ int i;
+ short s;
+};
+
+consteval long f(S s) {
+ long result = 0;
+ template for (auto x : s) { // OK, destructuring expansion statement
+ result += sizeof(x);
+ }
+ return result;
+}
+static_assert(f(S{}) == sizeof(int) + sizeof(short));
+}
+
+void not_constant_expression() {
+ template for (constexpr auto x : B()) { // expected-error {{constexpr variable '[__u0]' must be initialized by a constant expression}} \
+ expected-note {{reference to temporary is not a constant expression}} \
+ expected-note {{temporary created here}} \
+ expected-error {{constexpr variable 'x' must be initialized by a constant expression}} \
+ expected-note {{in instantiation of expansion statement requested here}} \
+ expected-note {{read of variable '[__u0]' whose value is not known}} \
+ expected-note {{declared here}}
+ g(x);
+ }
+}
+
+constexpr int references_enumerating() {
+ int x = 1, y = 2, z = 3;
+ template for (auto& x : {x, y, z}) { ++x; }
+ template for (auto&& x : {x, y, z}) { ++x; }
+ return x + y + z;
+}
+
+static_assert(references_enumerating() == 12);
+
+constexpr int references_destructuring() {
+ C c;
+ template for (auto& x : c) { ++x; }
+ template for (auto&& x : c) { ++x; }
+ return c.a + c.b + c.c;
+}
+
+static_assert(references_destructuring() == 12);
+
+constexpr int break_continue() {
+ int sum = 0;
+ template for (auto x : {1, 2}) {
+ break;
+ sum += x;
+ }
+
+ template for (auto x : {3, 4}) {
+ continue;
+ sum += x;
+ }
+
+ template for (auto x : {5, 6}) {
+ if (x == 6) break;
+ sum += x;
+ }
+
+ template for (auto x : {7, 8, 9}) {
+ if (x == 8) continue;
+ sum += x;
+ }
+
+ return sum;
+}
+
+static_assert(break_continue() == 21);
+
+constexpr int break_continue_nested() {
+ int sum = 0;
+
+ template for (auto x : {1, 2}) {
+ template for (auto y : {3, 4}) {
+ if (x == 2) break;
+ sum += y;
+ }
+ sum += x;
+ }
+
+ template for (auto x : {5, 6}) {
+ template for (auto y : {7, 8}) {
+ if (x == 6) continue;
+ sum += y;
+ }
+ sum += x;
+ }
+
+ return sum;
+}
+
+static_assert(break_continue_nested() == 36);
+
+template <typename ...Ts>
+void unexpanded_pack_bad(Ts ...ts) {
+ template for (auto x : ts) {} // expected-error {{expression contains unexpanded parameter pack 'ts'}}
+ template for (Ts x : {1, 2}) {} // expected-error {{declaration type contains unexpanded parameter pack 'Ts'}}
+ template for (auto x : {ts}) {} // expected-error {{initializer contains unexpanded parameter pack}} \
+ // expected-note {{in instantiation of expansion statement requested here}}
+}
+
+struct E { int x, y; constexpr E(int x, int y) : x{x}, y{y} {}};
+
+template <typename ...Es>
+constexpr int unexpanded_pack_good(Es ...es) {
+ int sum = 0;
+ ([&] {
+ template for (auto x : es) sum += x;
+ template for (Es e : {{5, 6}, {7, 8}}) sum += e.x + e.y;
+ }(), ...);
+ return sum;
+}
+
+static_assert(unexpanded_pack_good(E{1, 2}, E{3, 4}) == 62);
+
+// Ensure that the expansion-initializer is evaluated even if it expands
+// to nothing.
+//
+// This is related to CWG 3048. Note that we currently still model this as
+// a DecompositionDecl w/ zero bindings.
+constexpr bool empty_side_effect() {
+ struct A {
+ constexpr A(bool& b) {
+ b = true;
+ }
+ };
+
+ bool constructed = false;
+ template for (auto x : A(constructed)) static_assert(false);
+ return constructed;
+}
+
+static_assert(empty_side_effect());
+
+namespace apply_lifetime_extension {
+struct T {
+ int& x;
+ constexpr T(int& x) noexcept : x(x) {}
+ constexpr ~T() noexcept { x = 42; }
+};
+
+constexpr const T& f(const T& t) noexcept { return t; }
+constexpr T g(int& x) noexcept { return T(x); }
+
+// CWG 3043:
+//
+// Lifetime extension only applies to destructuring expansion statements
+// (enumerating statements don't have a range variable, and the range variable
+// of iterating statements is constexpr).
+constexpr int lifetime_extension() {
+ int x = 5;
+ int sum = 0;
+ template for (auto e : f(g(x))) {
+ sum += x;
+ }
+ return sum + x;
+}
+
+template <typename T>
+constexpr int lifetime_extension_instantiate_expansions() {
+ int x = 5;
+ int sum = 0;
+ template for (T e : f(g(x))) {
+ sum += x;
+ }
+ return sum + x;
+}
+
+template <typename T>
+constexpr int lifetime_extension_dependent_expansion_stmt() {
+ int x = 5;
+ int sum = 0;
+ template for (int e : f(g((T&)x))) {
+ sum += x;
+ }
+ return sum + x;
+}
+
+template <typename U>
+struct foo {
+ template <typename T>
+ constexpr int lifetime_extension_multiple_instantiations() {
+ int x = 5;
+ int sum = 0;
+ template for (T e : f(g((U&)x))) {
+ sum += x;
+ }
+ return sum + x;
+ }
+};
+
+static_assert(lifetime_extension() == 47);
+static_assert(lifetime_extension_instantiate_expansions<int>() == 47);
+static_assert(lifetime_extension_dependent_expansion_stmt<int>() == 47);
+static_assert(foo<int>().lifetime_extension_multiple_instantiations<int>() == 47);
+}
+
+template <typename... Ts>
+constexpr int return_from_expansion(Ts... ts) {
+ template for (int i : {1, 2, 3}) {
+ return (ts + ...);
+ }
+ __builtin_unreachable();
+}
+
+static_assert(return_from_expansion(4, 5, 6) == 15);
+
+void not_constexpr();
+
+constexpr int empty_expansion_consteval() {
+ template for (auto _ : {}) {
+ not_constexpr();
+ }
+ return 3;
+}
+
+static_assert(empty_expansion_consteval() == 3);
+
+void nested_empty_expansion() {
+ template for (auto x1 : {})
+ template for (auto x2 : {1})
+ static_assert(false);
+
+ template for (auto x1 : {1})
+ template for (auto x2 : {})
+ template for (auto x3 : {1})
+ static_assert(false);
+
+ template for (auto x1 : {})
+ template for (auto x2 : {})
+ template for (auto x3 : {})
+ template for (auto x4 : {1})
+ static_assert(false);
+
+ template for (auto x1 : {})
+ template for (auto x2 : {1})
+ template for (auto x3 : {})
+ template for (auto x4 : {1})
+ static_assert(false);
+
+ template for (auto x1 : {})
+ template for (auto x2 : {1})
+ template for (auto x4 : {1})
+ static_assert(false);
+}
+
+struct Empty {};
+
+template <typename T>
+void nested_empty_expansion_dependent() {
+ template for (auto x1 : T())
+ template for (auto x2 : {1})
+ static_assert(false);
+
+ template for (auto x1 : {1})
+ template for (auto x2 : T())
+ template for (auto x3 : {1})
+ static_assert(false);
+
+ template for (auto x1 : T())
+ template for (auto x2 : T())
+ template for (auto x3 : T())
+ template for (auto x4 : {1})
+ static_assert(false);
+
+ template for (auto x1 : T())
+ template for (auto x2 : {1})
+ template for (auto x3 : T())
+ template for (auto x4 : {1})
+ static_assert(false);
+
+ template for (auto x1 : T())
+ template for (auto x2 : {1})
+ template for (auto x4 : {1})
+ static_assert(false);
+}
+
+void nested_empty_expansion_dependent_instantiate() {
+ nested_empty_expansion_dependent<Empty>();
+}
+
+// Destructuring expansion statements using tuple_size/tuple_element/get.
+namespace std {
+template <typename>
+struct tuple_size;
+
+template <__SIZE_TYPE__, typename>
+struct tuple_element; // expected-note {{template is declared here}}
+
+namespace get_decomposition {
+struct MemberGet {
+ int x[6]{};
+
+ template <__SIZE_TYPE__ I>
+ constexpr int& get() { return x[I * 2]; }
+};
+
+struct ADLGet {
+ long x[8]{};
+};
+
+template <__SIZE_TYPE__ I>
+constexpr long& get(ADLGet& a) { return a.x[I * 2]; }
+} // namespace get_decomposition
+
+template <>
+struct tuple_size<get_decomposition::MemberGet> {
+ static constexpr __SIZE_TYPE__ value = 3;
+};
+
+template <__SIZE_TYPE__ I>
+struct tuple_element<I, get_decomposition::MemberGet> {
+ using type = int;
+};
+
+template <>
+struct tuple_size<get_decomposition::ADLGet> {
+ static constexpr __SIZE_TYPE__ value = 4;
+};
+
+template <__SIZE_TYPE__ I>
+struct tuple_element<I, get_decomposition::ADLGet> {
+ using type = long;
+};
+
+constexpr int member() {
+ get_decomposition::MemberGet m;
+ int v = 1;
+ template for (int& i : m) {
+ i = v;
+ v++;
+ }
+ return m.x[0] + m.x[2] + m.x[4];
+}
+
+constexpr long adl() {
+ get_decomposition::ADLGet m;
+ long v = 1;
+ template for (long& i : m) {
+ i = v;
+ v++;
+ }
+ return m.x[0] + m.x[2] + m.x[4] + m.x[6];
+}
+
+static_assert(member() == 6);
+static_assert(adl() == 10);
+
+struct TupleSizeOnly {};
+
+template <>
+struct tuple_size<TupleSizeOnly> {
+ static constexpr __SIZE_TYPE__ value = 3;
+};
+
+struct TupleSizeAndGet {
+ template <__SIZE_TYPE__>
+ constexpr int get() { return 1; }
+};
+
+template <>
+struct tuple_size<TupleSizeAndGet> {
+ static constexpr __SIZE_TYPE__ value = 3;
+};
+
+void invalid() {
+ template for (auto x : TupleSizeOnly()) {} // expected-error {{use of undeclared identifier 'get'}} \
+ expected-note {{in implicit initialization of binding declaration}}
+
+ template for (auto x : TupleSizeAndGet()) {} // expected-error {{implicit instantiation of undefined template 'std::tuple_element<0, std::TupleSizeAndGet>'}} \
+ expected-note {{in implicit initialization of binding declaration}}
+}
+} // namespace std
+
+constexpr int generic_lambda() {
+ static constexpr int arr[]{1, 2, 3};
+ int sum = 0;
+ [n = 5, &sum]<class = void>() {
+ template for (constexpr auto x : arr) {
+ sum += n + x;
+ }
+ }();
+ return sum;
+}
+
+static_assert(generic_lambda() == 21);
+
+void for_range_decl_must_be_var() {
+ template for (void q() : "error") // expected-error {{expansion statement declaration must declare a variable}}
+ ;
+}
+
+void init_list_bad() {
+ template for (auto y : {{1}, {2}, {3, {4}}, {{{5}}}}); // expected-error {{cannot deduce actual type for variable 'y' with type 'auto' from initializer list}} \
+ expected-note {{in instantiation of expansion statement requested here}}
+}
More information about the llvm-branch-commits
mailing list