Allow conditions to be decomposed with structured bindings

Summary:
This feature was discussed but not yet proposed.  It allows a structured binding to appear as a //condition//

    if (auto [ok, val] = f(...))

So the user can save an extra //condition// if the statement can test the value to-be-decomposed instead.  Formally, it makes the value of the underlying object of the structured binding declaration also the value of a //condition// that is an initialized declaration.

Considering its logicality which is entirely evident from its trivial implementation, I think it might be acceptable to land it as an extension for now before I write the paper.

Reviewers: rsmith, faisalv, aaron.ballman

Reviewed By: rsmith

Subscribers: aaron.ballman, cfe-commits

Differential Revision: https://reviews.llvm.org/D39284

llvm-svn: 320011
This commit is contained in:
Zhihao Yuan 2017-12-07 07:03:15 +00:00
parent b5deac444d
commit c81f4538ec
7 changed files with 183 additions and 13 deletions

View File

@ -414,6 +414,9 @@ def warn_cxx14_compat_decomp_decl : Warning<
"C++ standards before C++17">, DefaultIgnore, InGroup<CXXPre17Compat>;
def ext_decomp_decl : ExtWarn<
"decomposition declarations are a C++17 extension">, InGroup<CXX17>;
def ext_decomp_decl_cond : ExtWarn<
"ISO C++17 does not permit structured binding declaration in a condition">,
InGroup<DiagGroup<"binding-in-condition">>;
def err_decomp_decl_spec : Error<
"decomposition declaration cannot be declared "
"%plural{1:'%1'|:with '%1' specifiers}0">;

View File

@ -1995,9 +1995,9 @@ public:
case BlockContext:
case ForContext:
case InitStmtContext:
case ConditionContext:
return true;
case ConditionContext:
case MemberContext:
case PrototypeContext:
case TemplateParamContext:

View File

@ -1715,6 +1715,8 @@ Parser::ParseCXXTypeConstructExpression(const DeclSpec &DS) {
/// type-specifier-seq declarator '=' assignment-expression
/// [C++11] type-specifier-seq declarator '=' initializer-clause
/// [C++11] type-specifier-seq declarator braced-init-list
/// [Clang] type-specifier-seq ref-qualifier[opt] '[' identifier-list ']'
/// brace-or-equal-initializer
/// [GNU] type-specifier-seq declarator simple-asm-expr[opt] attributes[opt]
/// '=' assignment-expression
///

View File

@ -692,8 +692,9 @@ Sema::ActOnDecompositionDeclarator(Scope *S, Declarator &D,
assert(D.isDecompositionDeclarator());
const DecompositionDeclarator &Decomp = D.getDecompositionDeclarator();
// The syntax only allows a decomposition declarator as a simple-declaration
// or a for-range-declaration, but we parse it in more cases than that.
// The syntax only allows a decomposition declarator as a simple-declaration,
// a for-range-declaration, or a condition in Clang, but we parse it in more
// cases than that.
if (!D.mayHaveDecompositionDeclarator()) {
Diag(Decomp.getLSquareLoc(), diag::err_decomp_decl_context)
<< Decomp.getSourceRange();
@ -708,9 +709,12 @@ Sema::ActOnDecompositionDeclarator(Scope *S, Declarator &D,
return nullptr;
}
Diag(Decomp.getLSquareLoc(), getLangOpts().CPlusPlus17
? diag::warn_cxx14_compat_decomp_decl
: diag::ext_decomp_decl)
Diag(Decomp.getLSquareLoc(),
!getLangOpts().CPlusPlus17
? diag::ext_decomp_decl
: D.getContext() == Declarator::ConditionContext
? diag::ext_decomp_decl_cond
: diag::warn_cxx14_compat_decomp_decl)
<< Decomp.getSourceRange();
// The semantic context is always just the current context.

View File

@ -32,13 +32,14 @@ namespace OtherDecl {
void f(auto [a, b, c]); // expected-error {{'auto' not allowed in function prototype}} expected-error {{'a'}}
void g() {
// A condition is not a simple-declaration.
for (; auto [a, b, c] = S(); ) {} // expected-error {{not permitted in this context}}
if (auto [a, b, c] = S()) {} // expected-error {{not permitted in this context}}
if (int n; auto [a, b, c] = S()) {} // expected-error {{not permitted in this context}}
switch (auto [a, b, c] = S()) {} // expected-error {{not permitted in this context}}
switch (int n; auto [a, b, c] = S()) {} // expected-error {{not permitted in this context}}
while (auto [a, b, c] = S()) {} // expected-error {{not permitted in this context}}
// A condition is allowed as a Clang extension.
// See commentary in test/Parser/decomposed-condition.cpp
for (; auto [a, b, c] = S(); ) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
if (auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
if (int n; auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
switch (auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{statement requires expression of integer type ('S' invalid)}}
switch (int n; auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{statement requires expression of integer type ('S' invalid)}}
while (auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
// An exception-declaration is not a simple-declaration.
try {}

View File

@ -0,0 +1,61 @@
// RUN: %clang_cc1 -std=c++1z %s -verify
struct Na {
bool flag;
float data;
};
struct Rst {
bool flag;
float data;
explicit operator bool() const {
return flag;
}
};
Rst f();
Na g();
namespace CondInIf {
void h() {
if (auto [ok, d] = f()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
;
if (auto [ok, d] = g()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}}
;
}
} // namespace CondInIf
namespace CondInWhile {
void h() {
while (auto [ok, d] = f()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
;
while (auto [ok, d] = g()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}}
;
}
} // namespace CondInWhile
namespace CondInFor {
void h() {
for (; auto [ok, d] = f();) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
;
for (; auto [ok, d] = g();) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}}
;
}
} // namespace CondInFor
struct IntegerLike {
bool flag;
float data;
operator int() const {
return int(data);
}
};
namespace CondInSwitch {
void h(IntegerLike x) {
switch (auto [ok, d] = x) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
;
switch (auto [ok, d] = g()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{statement requires expression of integer type ('Na' invalid)}}
;
}
} // namespace CondInSwitch

View File

@ -0,0 +1,99 @@
// RUN: %clang_cc1 -std=c++1z -Wno-binding-in-condition -verify %s
struct X {
bool flag;
int data;
constexpr explicit operator bool() const {
return flag;
}
constexpr operator int() const {
return data;
}
};
namespace CondInIf {
constexpr int f(X x) {
if (auto [ok, d] = x)
return d + int(ok);
else
return d * int(ok);
ok = {}; // expected-error {{use of undeclared identifier 'ok'}}
d = {}; // expected-error {{use of undeclared identifier 'd'}}
}
static_assert(f({true, 2}) == 3);
static_assert(f({false, 2}) == 0);
constexpr char g(char const (&x)[2]) {
if (auto &[a, b] = x)
return a;
else
return b;
if (auto [a, b] = x) // expected-error {{an array type is not allowed here}}
;
}
static_assert(g("x") == 'x');
} // namespace CondInIf
namespace CondInSwitch {
constexpr int f(int n) {
switch (X s = {true, n}; auto [ok, d] = s) {
s = {};
case 0:
return int(ok);
case 1:
return d * 10;
case 2:
return d * 40;
default:
return 0;
}
ok = {}; // expected-error {{use of undeclared identifier 'ok'}}
d = {}; // expected-error {{use of undeclared identifier 'd'}}
s = {}; // expected-error {{use of undeclared identifier 's'}}
}
static_assert(f(0) == 1);
static_assert(f(1) == 10);
static_assert(f(2) == 80);
} // namespace CondInSwitch
namespace CondInWhile {
constexpr int f(int n) {
int m = 1;
while (auto [ok, d] = X{n > 1, n}) {
m *= d;
--n;
}
return m;
return ok; // expected-error {{use of undeclared identifier 'ok'}}
}
static_assert(f(0) == 1);
static_assert(f(1) == 1);
static_assert(f(4) == 24);
} // namespace CondInWhile
namespace CondInFor {
constexpr int f(int n) {
int a = 1, b = 1;
for (X x = {true, n}; auto &[ok, d] = x; --d) {
if (d < 2)
ok = false;
else {
int x = b;
b += a;
a = x;
}
}
return b;
return d; // expected-error {{use of undeclared identifier 'd'}}
}
static_assert(f(0) == 1);
static_assert(f(1) == 1);
static_assert(f(2) == 2);
static_assert(f(5) == 8);
} // namespace CondInFor