-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[Clang] Constexpr Structured Bindings : The easy parts #160337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Clang] Constexpr Structured Bindings : The easy parts #160337
Conversation
This implements the easy parts of P2686R5. Ie allowing constexpr structured vbinding 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.
@llvm/pr-subscribers-clang Author: Corentin Jabot (cor3ntin) ChangesThis implements the easy parts of P2686R5. 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. Full diff: https://github.com/llvm/llvm-project/pull/160337.diff 6 Files Affected:
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index eef9414668809..6e04bd4ae700a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -35,9 +35,8 @@ defm decomp_decl : CXX17Compat<"decomposition declarations are">;
defm inline_variable : CXX17Compat<"inline variables are">;
// C++20 compatibility with C++17 and earlier.
-defm decomp_decl_spec : CXX20Compat<
- "decomposition declaration declared "
- "%plural{1:'%1'|:with '%1' specifiers}0 is">;
+defm decomp_decl_spec
+ : CXX20Compat<"decomposition declaration declared '%0' is">;
defm constexpr_local_var_no_init : CXX20Compat<
"uninitialized variable in a constexpr %select{function|constructor}0 is">;
defm constexpr_function_try_block : CXX20Compat<
@@ -593,9 +592,8 @@ def warn_modifying_shadowing_decl :
// C++ decomposition declarations
def err_decomp_decl_context : Error<
"decomposition declaration not permitted in this context">;
-def err_decomp_decl_spec : Error<
- "decomposition declaration cannot be declared "
- "%plural{1:'%1'|:with '%1' specifiers}0">;
+def err_decomp_decl_spec
+ : Error<"decomposition declaration cannot be declared '%0'">;
def err_decomp_decl_type : Error<
"decomposition declaration cannot be declared with type %0; "
"declared type must be 'auto' or reference to 'auto'">;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index fb57b43882911..86ba474e064a3 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -29,6 +29,7 @@
#include "clang/AST/TypeOrdering.h"
#include "clang/Basic/AttributeCommonInfo.h"
#include "clang/Basic/PartialDiagnostic.h"
+#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Lex/LiteralSupport.h"
@@ -768,58 +769,44 @@ Sema::ActOnDecompositionDeclarator(Scope *S, Declarator &D,
// C++23 [dcl.pre]/6:
// Each decl-specifier in the decl-specifier-seq shall be static,
// thread_local, auto (9.2.9.6 [dcl.spec.auto]), or a cv-qualifier.
+ // C++23 [dcl.pre]/7:
+ // Each decl-specifier in the decl-specifier-seq shall be constexpr,
+ // constinit, static, thread_local, auto, or a cv-qualifier
auto &DS = D.getDeclSpec();
- {
- // Note: While constrained-auto needs to be checked, we do so separately so
- // we can emit a better diagnostic.
- SmallVector<StringRef, 8> BadSpecifiers;
- SmallVector<SourceLocation, 8> BadSpecifierLocs;
- SmallVector<StringRef, 8> CPlusPlus20Specifiers;
- SmallVector<SourceLocation, 8> CPlusPlus20SpecifierLocs;
- if (auto SCS = DS.getStorageClassSpec()) {
- if (SCS == DeclSpec::SCS_static) {
- CPlusPlus20Specifiers.push_back(DeclSpec::getSpecifierName(SCS));
- CPlusPlus20SpecifierLocs.push_back(DS.getStorageClassSpecLoc());
- } else {
- BadSpecifiers.push_back(DeclSpec::getSpecifierName(SCS));
- BadSpecifierLocs.push_back(DS.getStorageClassSpecLoc());
- }
- }
- if (auto TSCS = DS.getThreadStorageClassSpec()) {
- CPlusPlus20Specifiers.push_back(DeclSpec::getSpecifierName(TSCS));
- CPlusPlus20SpecifierLocs.push_back(DS.getThreadStorageClassSpecLoc());
- }
- if (DS.hasConstexprSpecifier()) {
- BadSpecifiers.push_back(
- DeclSpec::getSpecifierName(DS.getConstexprSpecifier()));
- BadSpecifierLocs.push_back(DS.getConstexprSpecLoc());
- }
- if (DS.isInlineSpecified()) {
- BadSpecifiers.push_back("inline");
- BadSpecifierLocs.push_back(DS.getInlineSpecLoc());
- }
-
- if (!BadSpecifiers.empty()) {
- auto &&Err = Diag(BadSpecifierLocs.front(), diag::err_decomp_decl_spec);
- Err << (int)BadSpecifiers.size()
- << llvm::join(BadSpecifiers.begin(), BadSpecifiers.end(), " ");
- // Don't add FixItHints to remove the specifiers; we do still respect
- // them when building the underlying variable.
- for (auto Loc : BadSpecifierLocs)
- Err << SourceRange(Loc, Loc);
- } else if (!CPlusPlus20Specifiers.empty()) {
- auto &&Warn = DiagCompat(CPlusPlus20SpecifierLocs.front(),
- diag_compat::decomp_decl_spec);
- Warn << (int)CPlusPlus20Specifiers.size()
- << llvm::join(CPlusPlus20Specifiers.begin(),
- CPlusPlus20Specifiers.end(), " ");
- for (auto Loc : CPlusPlus20SpecifierLocs)
- Warn << SourceRange(Loc, Loc);
- }
- // We can't recover from it being declared as a typedef.
- if (DS.getStorageClassSpec() == DeclSpec::SCS_typedef)
- return nullptr;
+ auto DiagBadSpecifier = [&](StringRef Name, SourceLocation Loc) {
+ Diag(Loc, diag::err_decomp_decl_spec) << Name;
+ };
+
+ auto DiagCpp20Specifier = [&](StringRef Name, SourceLocation Loc) {
+ DiagCompat(Loc, diag_compat::decomp_decl_spec) << Name;
+ };
+
+ if (auto SCS = DS.getStorageClassSpec()) {
+ if (SCS == DeclSpec::SCS_static)
+ DiagCpp20Specifier(DeclSpec::getSpecifierName(SCS),
+ DS.getStorageClassSpecLoc());
+ else
+ DiagBadSpecifier(DeclSpec::getSpecifierName(SCS),
+ DS.getStorageClassSpecLoc());
}
+ if (auto TSCS = DS.getThreadStorageClassSpec())
+ DiagCpp20Specifier(DeclSpec::getSpecifierName(TSCS),
+ DS.getThreadStorageClassSpecLoc());
+
+ if (DS.isInlineSpecified())
+ DiagBadSpecifier("inline", DS.getInlineSpecLoc());
+
+ if (ConstexprSpecKind ConstexprSpec = DS.getConstexprSpecifier();
+ ConstexprSpec != ConstexprSpecKind::Unspecified) {
+ if (ConstexprSpec == ConstexprSpecKind::Consteval ||
+ !getLangOpts().CPlusPlus26)
+ DiagBadSpecifier(DeclSpec::getSpecifierName(ConstexprSpec),
+ DS.getConstexprSpecLoc());
+ }
+
+ // We can't recover from it being declared as a typedef.
+ if (DS.getStorageClassSpec() == DeclSpec::SCS_typedef)
+ return nullptr;
// C++2a [dcl.struct.bind]p1:
// A cv that includes volatile is deprecated
diff --git a/clang/test/Parser/cxx1z-decomposition.cpp b/clang/test/Parser/cxx1z-decomposition.cpp
index b7a8d30bd16c5..274e24ea55522 100644
--- a/clang/test/Parser/cxx1z-decomposition.cpp
+++ b/clang/test/Parser/cxx1z-decomposition.cpp
@@ -83,11 +83,19 @@ namespace BadSpecifiers {
friend auto &[g] = n; // expected-error {{'auto' not allowed}} expected-error {{friends can only be classes or functions}}
};
typedef auto &[h] = n; // expected-error {{cannot be declared 'typedef'}}
- constexpr auto &[i] = n; // expected-error {{cannot be declared 'constexpr'}}
+ constexpr auto &[i] = n; // pre2c-error {{cannot be declared 'constexpr'}}
}
- static constexpr inline thread_local auto &[j1] = n; // expected-error {{cannot be declared with 'constexpr inline' specifiers}}
- static thread_local auto &[j2] = n; // cxx17-warning {{declared with 'static thread_local' specifiers is a C++20 extension}}
+ static constexpr inline thread_local auto &[j1] = n;
+ // pre2c-error@-1 {{cannot be declared 'constexpr'}} \
+ // expected-error@-1 {{cannot be declared 'inline'}} \
+ // cxx17-warning@-1 {{declared 'static' is a C++20 extension}} \
+ // cxx17-warning@-1 {{declared 'thread_local' is a C++20 extension}}
+
+ static thread_local auto &[j2] = n;
+ // cxx17-warning@-1 {{declared 'static' is a C++20 extension}}\
+ // cxx17-warning@-1 {{declared 'thread_local' is a C++20 extension}}
+
inline auto &[k] = n; // expected-error {{cannot be declared 'inline'}}
diff --git a/clang/test/SemaCXX/cxx17-compat.cpp b/clang/test/SemaCXX/cxx17-compat.cpp
index 81b3e1fde5493..99e41d818a6c3 100644
--- a/clang/test/SemaCXX/cxx17-compat.cpp
+++ b/clang/test/SemaCXX/cxx17-compat.cpp
@@ -83,9 +83,11 @@ static auto [cx, cy, cz] = C();
void f() {
static thread_local auto [cx, cy, cz] = C();
#if __cplusplus <= 201703L
- // expected-warning@-2 {{decomposition declaration declared with 'static thread_local' specifiers is a C++20 extension}}
+ // expected-warning@-2 {{decomposition declaration declared 'static' is a C++20 extension}}
+ // expected-warning@-3 {{decomposition declaration declared 'thread_local' is a C++20 extension}}
#else
- // expected-warning@-4 {{decomposition declaration declared with 'static thread_local' specifiers is incompatible with C++ standards before C++20}}
+ // expected-warning@-5 {{decomposition declaration declared 'static' is incompatible with C++ standards before C++20}}
+ // expected-warning@-6 {{decomposition declaration declared 'thread_local' is incompatible with C++ standards before C++20}}
#endif
}
diff --git a/clang/test/SemaCXX/cxx2c-binding-pack-nontemplate.cpp b/clang/test/SemaCXX/cxx2c-binding-pack-nontemplate.cpp
index a4f0bcdb4270b..638a2d805c2c5 100644
--- a/clang/test/SemaCXX/cxx2c-binding-pack-nontemplate.cpp
+++ b/clang/test/SemaCXX/cxx2c-binding-pack-nontemplate.cpp
@@ -10,8 +10,8 @@ void decompose_array() {
auto [x, ...rest, y] = arr;
// cxx26-warning@+4 {{structured binding packs are incompatible with C++ standards before C++2c}}
- // cxx23-warning@+3 {{structured binding packs are a C++2c extension}}
- // nontemplate-error@+2 {{decomposition declaration cannot be declared 'constexpr'}}
+ // cxx23-error@+3 {{decomposition declaration cannot be declared 'constexpr'}}
+ // cxx23-warning@+2 {{structured binding packs are a C++2c extension}}
// nontemplate-error@+1 {{pack declaration outside of template}}
constexpr auto [x_c, ...rest_c, y_c] = arr;
}
diff --git a/clang/test/SemaCXX/cxx2c-decomposition.cpp b/clang/test/SemaCXX/cxx2c-decomposition.cpp
new file mode 100644
index 0000000000000..99278c6575ef1
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-decomposition.cpp
@@ -0,0 +1,76 @@
+// RUN: %clang_cc1 -std=c++2c %s -triple x86_64-unknown-linux-gnu -verify=expected
+// RUN: %clang_cc1 -std=c++2c %s -triple x86_64-unknown-linux-gnu -verify=expected -fexperimental-new-constant-interpreter
+
+namespace std {
+ using size_t = decltype(sizeof(0));
+ template<typename> struct tuple_size;
+ template<size_t, typename> struct tuple_element;
+}
+
+struct Y { int n = 0; };
+struct X { X(); X(Y); X(const X&); ~X(); int k = 42;}; // #X-decl
+struct Z { constexpr Z(): i (43){}; int i;}; // #Z-decl
+struct Z2 { constexpr Z2(): i (0){}; int i; ~Z2();}; // #Z2-decl
+
+struct Bit { constexpr Bit(): i(1), j(1){}; int i: 2; int j:2;};
+
+struct A { int a : 13; bool b; };
+
+struct B {};
+template<> struct std::tuple_size<B> { enum { value = 2 }; };
+template<> struct std::tuple_size<const B> { enum { value = 2 }; };
+template<> struct std::tuple_element<0, const B> { using type = Y; };
+template<> struct std::tuple_element<1, const B> { using type = const int&; };
+template<int N>
+constexpr auto get(B) {
+ if constexpr (N == 0)
+ return Y();
+ else
+ return 0.0;
+}
+
+
+constexpr auto [t1] = Y {42};
+static_assert(t1 == 42);
+
+constexpr int i[] = {1, 2};
+constexpr auto [t2, t3] = i;
+static_assert(t2 == 1);
+static_assert(t3 == 2);
+
+constexpr auto [t4] = X();
+// expected-error@-1 {{constexpr variable cannot have non-literal type 'const X'}} \
+// 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}}
+
+constexpr auto [t5] = Z();
+static_assert(t5 == 43);
+
+constexpr auto [t6] = Z2();
+//expected-error@-1 {{constexpr variable cannot have non-literal type 'const Z2'}}
+// expected-note@#Z2-decl {{'Z2' is not literal because its destructor is not constexpr}}
+
+constexpr auto [t7, t8] = Bit();
+static_assert(t7 == 1);
+static_assert(t8 == 1);
+
+void test_tpl(auto) {
+ constexpr auto [...p] = Bit();
+ static_assert(((p == 1) && ...));
+}
+
+void test() {
+ test_tpl(0);
+}
+
+// FIXME : support tuple
+constexpr auto [a, b] = B{};
+static_assert(a.n == 0);
+// expected-error@-1 {{static assertion expression is not an integral constant expression}} \
+// expected-note@-1 {{read of temporary is not allowed in a constant expression outside the expression that created the temporary}}\
+// expected-note@-2 {{temporary created here}}
+
+constinit auto [init1] = Y {42};
+constinit auto [init2] = X {}; // expected-error {{variable does not have a constant initializer}} \
+// expected-note {{required by 'constinit' specifier here}} \
+// expected-note {{non-constexpr constructor 'X' cannot be used in a constant expression}} \
+// expected-note@#X-decl {{declared here}}
|
5a785ca
to
c69bccf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a release note here? Though one that I'd expect we edit along the way.
^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
- Started the implementation of `P2686R5 <https://wg21.link/P2686R5>`_ Constexpr structured bindings. | ||
At this timem, references to constexpr and decomposition of *tuple-like* types are not supported |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: timem
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.
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.