Skip to content

Commit 83331cc

Browse files
authored
[Clang] Constexpr Structured Bindings : The easy parts (#160337)
This implements the easy parts of P2686R5. Ie allowing constexpr structured binding of structs and arrays. References to constexpr variables / support for tuple is left for a future PR. Until we implement the whole thing, the feature is not enabled as an extension in older language modes. Trying to use it as a tuple does produce errors but not meaningful ones. We could add a better diagnostic if we fail to complete the implementation before the end of the clang 22 cycle.
1 parent c989283 commit 83331cc

File tree

7 files changed

+137
-63
lines changed

7 files changed

+137
-63
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ C++ Language Changes
150150
C++2c Feature Support
151151
^^^^^^^^^^^^^^^^^^^^^
152152

153+
- Started the implementation of `P2686R5 <https://wg21.link/P2686R5>`_ Constexpr structured bindings.
154+
At this timem, references to constexpr and decomposition of *tuple-like* types are not supported
155+
(only arrays and aggregates are).
156+
153157
C++23 Feature Support
154158
^^^^^^^^^^^^^^^^^^^^^
155159

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ defm decomp_decl : CXX17Compat<"decomposition declarations are">;
3535
defm inline_variable : CXX17Compat<"inline variables are">;
3636

3737
// C++20 compatibility with C++17 and earlier.
38-
defm decomp_decl_spec : CXX20Compat<
39-
"decomposition declaration declared "
40-
"%plural{1:'%1'|:with '%1' specifiers}0 is">;
38+
defm decomp_decl_spec
39+
: CXX20Compat<"decomposition declaration declared '%0' is">;
4140
defm constexpr_local_var_no_init : CXX20Compat<
4241
"uninitialized variable in a constexpr %select{function|constructor}0 is">;
4342
defm constexpr_function_try_block : CXX20Compat<
@@ -593,9 +592,8 @@ def warn_modifying_shadowing_decl :
593592
// C++ decomposition declarations
594593
def err_decomp_decl_context : Error<
595594
"decomposition declaration not permitted in this context">;
596-
def err_decomp_decl_spec : Error<
597-
"decomposition declaration cannot be declared "
598-
"%plural{1:'%1'|:with '%1' specifiers}0">;
595+
def err_decomp_decl_spec
596+
: Error<"decomposition declaration cannot be declared '%0'">;
599597
def err_decomp_decl_type : Error<
600598
"decomposition declaration cannot be declared with type %0; "
601599
"declared type must be 'auto' or reference to 'auto'">;

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 36 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -768,58 +768,44 @@ Sema::ActOnDecompositionDeclarator(Scope *S, Declarator &D,
768768
// C++23 [dcl.pre]/6:
769769
// Each decl-specifier in the decl-specifier-seq shall be static,
770770
// thread_local, auto (9.2.9.6 [dcl.spec.auto]), or a cv-qualifier.
771+
// C++23 [dcl.pre]/7:
772+
// Each decl-specifier in the decl-specifier-seq shall be constexpr,
773+
// constinit, static, thread_local, auto, or a cv-qualifier
771774
auto &DS = D.getDeclSpec();
772-
{
773-
// Note: While constrained-auto needs to be checked, we do so separately so
774-
// we can emit a better diagnostic.
775-
SmallVector<StringRef, 8> BadSpecifiers;
776-
SmallVector<SourceLocation, 8> BadSpecifierLocs;
777-
SmallVector<StringRef, 8> CPlusPlus20Specifiers;
778-
SmallVector<SourceLocation, 8> CPlusPlus20SpecifierLocs;
779-
if (auto SCS = DS.getStorageClassSpec()) {
780-
if (SCS == DeclSpec::SCS_static) {
781-
CPlusPlus20Specifiers.push_back(DeclSpec::getSpecifierName(SCS));
782-
CPlusPlus20SpecifierLocs.push_back(DS.getStorageClassSpecLoc());
783-
} else {
784-
BadSpecifiers.push_back(DeclSpec::getSpecifierName(SCS));
785-
BadSpecifierLocs.push_back(DS.getStorageClassSpecLoc());
786-
}
787-
}
788-
if (auto TSCS = DS.getThreadStorageClassSpec()) {
789-
CPlusPlus20Specifiers.push_back(DeclSpec::getSpecifierName(TSCS));
790-
CPlusPlus20SpecifierLocs.push_back(DS.getThreadStorageClassSpecLoc());
791-
}
792-
if (DS.hasConstexprSpecifier()) {
793-
BadSpecifiers.push_back(
794-
DeclSpec::getSpecifierName(DS.getConstexprSpecifier()));
795-
BadSpecifierLocs.push_back(DS.getConstexprSpecLoc());
796-
}
797-
if (DS.isInlineSpecified()) {
798-
BadSpecifiers.push_back("inline");
799-
BadSpecifierLocs.push_back(DS.getInlineSpecLoc());
800-
}
801-
802-
if (!BadSpecifiers.empty()) {
803-
auto &&Err = Diag(BadSpecifierLocs.front(), diag::err_decomp_decl_spec);
804-
Err << (int)BadSpecifiers.size()
805-
<< llvm::join(BadSpecifiers.begin(), BadSpecifiers.end(), " ");
806-
// Don't add FixItHints to remove the specifiers; we do still respect
807-
// them when building the underlying variable.
808-
for (auto Loc : BadSpecifierLocs)
809-
Err << SourceRange(Loc, Loc);
810-
} else if (!CPlusPlus20Specifiers.empty()) {
811-
auto &&Warn = DiagCompat(CPlusPlus20SpecifierLocs.front(),
812-
diag_compat::decomp_decl_spec);
813-
Warn << (int)CPlusPlus20Specifiers.size()
814-
<< llvm::join(CPlusPlus20Specifiers.begin(),
815-
CPlusPlus20Specifiers.end(), " ");
816-
for (auto Loc : CPlusPlus20SpecifierLocs)
817-
Warn << SourceRange(Loc, Loc);
818-
}
819-
// We can't recover from it being declared as a typedef.
820-
if (DS.getStorageClassSpec() == DeclSpec::SCS_typedef)
821-
return nullptr;
775+
auto DiagBadSpecifier = [&](StringRef Name, SourceLocation Loc) {
776+
Diag(Loc, diag::err_decomp_decl_spec) << Name;
777+
};
778+
779+
auto DiagCpp20Specifier = [&](StringRef Name, SourceLocation Loc) {
780+
DiagCompat(Loc, diag_compat::decomp_decl_spec) << Name;
781+
};
782+
783+
if (auto SCS = DS.getStorageClassSpec()) {
784+
if (SCS == DeclSpec::SCS_static)
785+
DiagCpp20Specifier(DeclSpec::getSpecifierName(SCS),
786+
DS.getStorageClassSpecLoc());
787+
else
788+
DiagBadSpecifier(DeclSpec::getSpecifierName(SCS),
789+
DS.getStorageClassSpecLoc());
822790
}
791+
if (auto TSCS = DS.getThreadStorageClassSpec())
792+
DiagCpp20Specifier(DeclSpec::getSpecifierName(TSCS),
793+
DS.getThreadStorageClassSpecLoc());
794+
795+
if (DS.isInlineSpecified())
796+
DiagBadSpecifier("inline", DS.getInlineSpecLoc());
797+
798+
if (ConstexprSpecKind ConstexprSpec = DS.getConstexprSpecifier();
799+
ConstexprSpec != ConstexprSpecKind::Unspecified) {
800+
if (ConstexprSpec == ConstexprSpecKind::Consteval ||
801+
!getLangOpts().CPlusPlus26)
802+
DiagBadSpecifier(DeclSpec::getSpecifierName(ConstexprSpec),
803+
DS.getConstexprSpecLoc());
804+
}
805+
806+
// We can't recover from it being declared as a typedef.
807+
if (DS.getStorageClassSpec() == DeclSpec::SCS_typedef)
808+
return nullptr;
823809

824810
// C++2a [dcl.struct.bind]p1:
825811
// A cv that includes volatile is deprecated

clang/test/Parser/cxx1z-decomposition.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,19 @@ namespace BadSpecifiers {
8383
friend auto &[g] = n; // expected-error {{'auto' not allowed}} expected-error {{friends can only be classes or functions}}
8484
};
8585
typedef auto &[h] = n; // expected-error {{cannot be declared 'typedef'}}
86-
constexpr auto &[i] = n; // expected-error {{cannot be declared 'constexpr'}}
86+
constexpr auto &[i] = n; // pre2c-error {{cannot be declared 'constexpr'}}
8787
}
8888

89-
static constexpr inline thread_local auto &[j1] = n; // expected-error {{cannot be declared with 'constexpr inline' specifiers}}
90-
static thread_local auto &[j2] = n; // cxx17-warning {{declared with 'static thread_local' specifiers is a C++20 extension}}
89+
static constexpr inline thread_local auto &[j1] = n;
90+
// pre2c-error@-1 {{cannot be declared 'constexpr'}} \
91+
// expected-error@-1 {{cannot be declared 'inline'}} \
92+
// cxx17-warning@-1 {{declared 'static' is a C++20 extension}} \
93+
// cxx17-warning@-1 {{declared 'thread_local' is a C++20 extension}}
94+
95+
static thread_local auto &[j2] = n;
96+
// cxx17-warning@-1 {{declared 'static' is a C++20 extension}}\
97+
// cxx17-warning@-1 {{declared 'thread_local' is a C++20 extension}}
98+
9199

92100
inline auto &[k] = n; // expected-error {{cannot be declared 'inline'}}
93101

clang/test/SemaCXX/cxx17-compat.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@ static auto [cx, cy, cz] = C();
8383
void f() {
8484
static thread_local auto [cx, cy, cz] = C();
8585
#if __cplusplus <= 201703L
86-
// expected-warning@-2 {{decomposition declaration declared with 'static thread_local' specifiers is a C++20 extension}}
86+
// expected-warning@-2 {{decomposition declaration declared 'static' is a C++20 extension}}
87+
// expected-warning@-3 {{decomposition declaration declared 'thread_local' is a C++20 extension}}
8788
#else
88-
// expected-warning@-4 {{decomposition declaration declared with 'static thread_local' specifiers is incompatible with C++ standards before C++20}}
89+
// expected-warning@-5 {{decomposition declaration declared 'static' is incompatible with C++ standards before C++20}}
90+
// expected-warning@-6 {{decomposition declaration declared 'thread_local' is incompatible with C++ standards before C++20}}
8991
#endif
9092
}
9193

clang/test/SemaCXX/cxx2c-binding-pack-nontemplate.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ void decompose_array() {
1010
auto [x, ...rest, y] = arr;
1111

1212
// cxx26-warning@+4 {{structured binding packs are incompatible with C++ standards before C++2c}}
13-
// cxx23-warning@+3 {{structured binding packs are a C++2c extension}}
14-
// nontemplate-error@+2 {{decomposition declaration cannot be declared 'constexpr'}}
13+
// cxx23-error@+3 {{decomposition declaration cannot be declared 'constexpr'}}
14+
// cxx23-warning@+2 {{structured binding packs are a C++2c extension}}
1515
// nontemplate-error@+1 {{pack declaration outside of template}}
1616
constexpr auto [x_c, ...rest_c, y_c] = arr;
1717
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// RUN: %clang_cc1 -std=c++2c %s -triple x86_64-unknown-linux-gnu -verify=expected
2+
// RUN: %clang_cc1 -std=c++2c %s -triple x86_64-unknown-linux-gnu -verify=expected -fexperimental-new-constant-interpreter
3+
4+
namespace std {
5+
using size_t = decltype(sizeof(0));
6+
template<typename> struct tuple_size;
7+
template<size_t, typename> struct tuple_element;
8+
}
9+
10+
struct Y { int n = 0; };
11+
struct X { X(); X(Y); X(const X&); ~X(); int k = 42;}; // #X-decl
12+
struct Z { constexpr Z(): i (43){}; int i;}; // #Z-decl
13+
struct Z2 { constexpr Z2(): i (0){}; int i; ~Z2();}; // #Z2-decl
14+
15+
struct Bit { constexpr Bit(): i(1), j(1){}; int i: 2; int j:2;};
16+
17+
struct A { int a : 13; bool b; };
18+
19+
struct B {};
20+
template<> struct std::tuple_size<B> { enum { value = 2 }; };
21+
template<> struct std::tuple_size<const B> { enum { value = 2 }; };
22+
template<> struct std::tuple_element<0, const B> { using type = Y; };
23+
template<> struct std::tuple_element<1, const B> { using type = const int&; };
24+
template<int N>
25+
constexpr auto get(B) {
26+
if constexpr (N == 0)
27+
return Y();
28+
else
29+
return 0.0;
30+
}
31+
32+
33+
constexpr auto [t1] = Y {42};
34+
static_assert(t1 == 42);
35+
36+
constexpr int i[] = {1, 2};
37+
constexpr auto [t2, t3] = i;
38+
static_assert(t2 == 1);
39+
static_assert(t3 == 2);
40+
41+
constexpr auto [t4] = X();
42+
// expected-error@-1 {{constexpr variable cannot have non-literal type 'const X'}} \
43+
// expected-note@#X-decl {{'X' is not literal because it is not an aggregate and has no constexpr constructors other than copy or move constructors}}
44+
45+
constexpr auto [t5] = Z();
46+
static_assert(t5 == 43);
47+
48+
constexpr auto [t6] = Z2();
49+
//expected-error@-1 {{constexpr variable cannot have non-literal type 'const Z2'}}
50+
// expected-note@#Z2-decl {{'Z2' is not literal because its destructor is not constexpr}}
51+
52+
constexpr auto [t7, t8] = Bit();
53+
static_assert(t7 == 1);
54+
static_assert(t8 == 1);
55+
56+
void test_tpl(auto) {
57+
constexpr auto [...p] = Bit();
58+
static_assert(((p == 1) && ...));
59+
}
60+
61+
void test() {
62+
test_tpl(0);
63+
}
64+
65+
// FIXME : support tuple
66+
constexpr auto [a, b] = B{};
67+
static_assert(a.n == 0);
68+
// expected-error@-1 {{static assertion expression is not an integral constant expression}} \
69+
// expected-note@-1 {{read of temporary is not allowed in a constant expression outside the expression that created the temporary}}\
70+
// expected-note@-2 {{temporary created here}}
71+
72+
constinit auto [init1] = Y {42};
73+
constinit auto [init2] = X {}; // expected-error {{variable does not have a constant initializer}} \
74+
// expected-note {{required by 'constinit' specifier here}} \
75+
// expected-note {{non-constexpr constructor 'X' cannot be used in a constant expression}} \
76+
// expected-note@#X-decl {{declared here}}

0 commit comments

Comments
 (0)