Clarification on decomposition declarations (a.k.a. structured bindings)

950 views
Skip to first unread message

rom...@google.com

unread,
Jan 3, 2017, 8:15:33 AM1/3/17
to ISO C++ Standard - Discussion

Hello,

It’s not clear to me from [dcl.decomp] whether the synthetic variable e is defined before v0, v1, v2, .... Consider the following program.

int a[] = {0};

int main() { auto& [a] = a; }

The latest draft says that “[f]irst, a variable with a unique name e is introduced”, which I assume to mean that the program is roughly equivalent to the following and is thus valid.

int a[] = {0};

int main() {
  int (&e)[1] = a;
  int& a = e[0];
}

Is my understanding correct? This behavior would be in line with how range-based for loops work.

int main() {
  int a[] = {0};
  for (int a : a) {}  // this is valid
}

A related question. Can decomposition declarations be static and/or constexpr?

int a[] = {0};
int main() { static constexpr auto& [x] = a; }

I believe this is valid. Is that right?

Roman Perepelitsa.

Richard Smith

unread,
Jan 3, 2017, 2:57:09 PM1/3/17
to std-dis...@isocpp.org
On 3 January 2017 at 05:15, romanp via ISO C++ Standard - Discussion <std-dis...@isocpp.org> wrote:

Hello,

It’s not clear to me from [dcl.decomp] whether the synthetic variable e is defined before v0, v1, v2, .... Consider the following program.

int a[] = {0};

int main() { auto& [a] = a; }

The latest draft says that “[f]irst, a variable with a unique name e is introduced”, which I assume to mean that the program is roughly equivalent to the following and is thus valid.

int a[] = {0};

int main() {
  int (&e)[1] = a;
  int& a = e[0];
}

Is my understanding correct?

No. [basic.scope.pdecl]/1: "The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its
initializer (if any), except as noted below."

So the name 'a' in the initializer denotes the binding, not ::a, and the declaration is ill-formed by a liberal interpretation of [dcl.spec.auto]p10 ("If the type of an entity with an undeduced placeholder type is needed to determine the type of an expression, the program is ill-formed.")
 

This behavior would be in line with how range-based for loops work.

int main() {
  int a[] = {0};
  for (int a : a) {}  // this is valid
}

A related question. Can decomposition declarations be static and/or constexpr?

 No. See [dcl.dcl]p8: "The decl-specifier-seq shall contain only the type-specifier auto (7.1.7.4) and cv-qualifiers."

I expect that rule to get relaxed soon, though it's not clear if "soon" will be before or after C++17.
int a[] = {0};
int main() { static constexpr auto& [x] = a; }

I believe this is valid. Is that right?

Roman Perepelitsa.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.

Roman

unread,
Jan 4, 2017, 5:10:32 AM1/4/17
to std-dis...@isocpp.org

On Tue, Jan 3, 2017 at 8:56 PM, Richard Smith <ric...@metafoo.co.uk> wrote:

On 3 January 2017 at 05:15, romanp via ISO C++ Standard - Discussion <std-dis...@isocpp.org> wrote:

Hello,

It’s not clear to me from [dcl.decomp] whether the synthetic variable e is defined before v0, v1, v2, .... Consider the following program.

int a[] = {0};

int main() { auto& [a] = a; }

The latest draft says that “[f]irst, a variable with a unique name e is introduced”, which I assume to mean that the program is roughly equivalent to the following and is thus valid.

int a[] = {0};

int main() {
  int (&e)[1] = a;
  int& a = e[0];
}

Is my understanding correct?

No. [basic.scope.pdecl]/1: "The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its
initializer (if any), except as noted below."

If I understand your point correctly, you are saying that std::make_tuple(1, 2) in the following snippet is the initializer of x and y.

auto [x, y] = std::make_tuple(1, 2);

This is not obvious from the standard and feels weird. How can an initializer for an int be a tuple?

I think [dcl.decomp]/1 says that std::make_tuple(1, 2) is the initializer of the synthetic variable e.

[...] e is defined as-if by
 
        attribute-specifier-seqopt decl-specifier-seq ref-qualifieropt e initializer ;

And then [dcl.decomp]/3 says that the initializers for x and y are get<0>(e) and get<1>(e) respectively.

[...] the initializer is get<i>(e) [...]
[...] each vi is a variable of type “reference to Ti” initialized with the initializer [...]

This seems to imply that my original example is valid.

A related question. Can decomposition declarations be static and/or constexpr?

 No. See [dcl.dcl]p8: "The decl-specifier-seq shall contain only the type-specifier auto (7.1.7.4) and cv-qualifiers."

I expect that rule to get relaxed soon, though it's not clear if "soon" will be before or after C++17.

I hope it happens before C++17 is finalized. It would make decomposition declarations more regular. Fewer special cases for the practitioners to remember.

Roman Perepelitsa.

Richard Smith

unread,
Jan 9, 2017, 6:05:02 PM1/9/17
to std-dis...@isocpp.org
On 4 January 2017 at 02:10, 'Roman' via ISO C++ Standard - Discussion <std-dis...@isocpp.org> wrote:

On Tue, Jan 3, 2017 at 8:56 PM, Richard Smith <ric...@metafoo.co.uk> wrote:

On 3 January 2017 at 05:15, romanp via ISO C++ Standard - Discussion <std-dis...@isocpp.org> wrote:

Hello,

It’s not clear to me from [dcl.decomp] whether the synthetic variable e is defined before v0, v1, v2, .... Consider the following program.

int a[] = {0};

int main() { auto& [a] = a; }

The latest draft says that “[f]irst, a variable with a unique name e is introduced”, which I assume to mean that the program is roughly equivalent to the following and is thus valid.

int a[] = {0};

int main() {
  int (&e)[1] = a;
  int& a = e[0];
}

Is my understanding correct?

No. [basic.scope.pdecl]/1: "The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its
initializer (if any), except as noted below."

If I understand your point correctly, you are saying that std::make_tuple(1, 2) in the following snippet is the initializer of x and y.

auto [x, y] = std::make_tuple(1, 2);

This is not obvious from the standard and feels weird. How can an initializer for an int be a tuple?

The quoted text says the point of declaration is "immediately after its complete declarator", which is presumably supposed to be the "[x, y]" piece in this case. I agree that's not very clear.

I think [dcl.decomp]/1 says that std::make_tuple(1, 2) is the initializer of the synthetic variable e.

[...] e is defined as-if by
 
        attribute-specifier-seqopt decl-specifier-seq ref-qualifieropt e initializer ;

And then [dcl.decomp]/3 says that the initializers for x and y are get<0>(e) and get<1>(e) respectively.

The above doesn't exactly specify a syntactic rewrite, unlike the range-based for loop case, so I don't think it can be used to infer point of declaration.

In the original case, we don't use the tuple_size interpretation. x and y are not variables and they do not have initializers. They are just names that can be used as lvalues to denote the elements e[0] and e[1]; see [dcl.decomp]/2. So there isn't even a rewrite argument to suggest that they are in scope in the initializer.

[...] the initializer is get<i>(e) [...]
[...] each vi is a variable of type “reference to Ti” initialized with the initializer [...]

This seems to imply that my original example is valid.

I don't think that follows. But I agree the wording here is incomplete.

A related question. Can decomposition declarations be static and/or constexpr?

 No. See [dcl.dcl]p8: "The decl-specifier-seq shall contain only the type-specifier auto (7.1.7.4) and cv-qualifiers."

I expect that rule to get relaxed soon, though it's not clear if "soon" will be before or after C++17.

I hope it happens before C++17 is finalized. It would make decomposition declarations more regular. Fewer special cases for the practitioners to remember.

I agree. I filed an NB comment on this. EWG voting went down like this:

  We should write a paper for US 95, GB 16, GB 17? 12 | 6 | 3 | 0 | 0
  We should write this for C++17? 0 | 2 | 13 | 3 | 3

... which to me suggests that this is unlikely to make C++17. Especially not if no-one writes that paper :)
Reply all
Reply to author
Forward
0 new messages