structured binding: desirable futur features

337 views
Skip to first unread message

Daemon Snake

unread,
Jul 20, 2017, 7:37:24 PM7/20/17
to ISO C++ Standard - Future Proposals
Hi,

So I've been reading the past discussions surrounding structured binding.
It is my understanding that due to time constraints the proposal was really bare bone and that some possible improvements should be checked in the future.

This got me quite inspired and I'm waiting for some feedback.
Disclaimer: Most of them depends on constexpr structured binding.

I. Declarator specifiers for structured binding (useful, desirable)

allowing constexpr, static, thread_local specifiers.
if constexpr (constexpr auto[state, value] = ...;  state) {}

II. initialization from a braced-init-list (trivial, one use case)

While reading the old P0144R0 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0144r0.pdf) the section 3.2 caught my attention.
3.2 Should this syntax support initialization from a braced-init-list?

For example:
auto {x,y,z} = {1, xyzzys, 3.14159}; // NOT proposed We think the answer should be no.

This would be trivial to add, but should be well motivated and we know of no use cases where this offers additional expressive power not already available (and with greater clarity) using individual variable declarations."

I think I've found a use case. Not a big one, but still
for (auto[x, y, z] = {1, "xyzzy"s, 3.14159}; ...; ...) {}

Which would be the same as:
for (auto[x, y, z] = std::tuple{1, "xyzzy"s, 3.14159}; ...; ...) {}

But without having to use std::tuple.

III. Parameter pack through structured binding (controversial?, useful, complex to implement?)

auto[x...] = something;

This would allow std::apply to be expressed much simpler
template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t)
{
   
auto&&[values...] = std::forward<T>(t);
   
return std::invoke(std::forward<F>(f), values...);
}

solving the unresolved function overload issue of std::apply by users
template <class... Args> void print(Args... args);

auto[values...] = func(...);
print(values...);

making std::make_index_sequence's use much easier
//uses std::make_index_sequence
template <size_t End> constexpr auto make_index_sequence_array() -> std::array<size_t, End>;

template <size_t From, size_t To, class Tuple>
constexpr auto select_tuple_part(Tuple&& tuple)
{
   
using T = std::decay_t<Tuple>;
   
static_assert(From < To && To < std::tuple_size_v<T> && From < std::tuple_size_v<T>);
   
constexpr auto[I...] = make_index_sequence_array<To - From>();
   
return std::tuple{std::get<(I+From)...>(tuple)};
}

template <class Tuple>
constexpr auto reverse_tuple(Tuple&& tuple)
{
   
using T = std::decay_t<Tuple>;
   
constexpr size_t len = std::tuple_size_v<T>;
   
if constexpr (len == 0)
       
return std::tuple{};
   
else
   
{
       
constexpr auto[I...] = make_index_sequence_array<len>();
       
return std::tuple{std::get<len - I - 1>(tuple)...};
   
}
}

IV. structured binding in template lists (controversial, user class non-type parameter, complex, require auto[x...], N3413 is better)

I believe this could solve N3413's mangling issues and member value vs operator== dilemmas.
Disclaimer: It would also require something like nested structured binding to be really useful with user types and wouldn't solve the floating point non-type template parameter issue.
template<auto[values....]> struct A;

template <class CharT, CharT... Chars> constexpr auto operator""_s() -> std::array<CharT, sizeof...(Chars)>;

A<"Hello"_s> tmp;

Conclusion
Thanks for the read and please let me know what you think.

Nicol Bolas

unread,
Jul 20, 2017, 8:20:00 PM7/20/17
to ISO C++ Standard - Future Proposals
On Thursday, July 20, 2017 at 7:37:24 PM UTC-4, Daemon Snake wrote:
Hi,

So I've been reading the past discussions surrounding structured binding.
It is my understanding that due to time constraints the proposal was really bare bone and that some possible improvements should be checked in the future.

This got me quite inspired and I'm waiting for some feedback.
Disclaimer: Most of them depends on constexpr structured binding.

I. Declarator specifiers for structured binding (useful, desirable)

allowing constexpr, static, thread_local specifiers.
if constexpr (constexpr auto[state, value] = ...;  state) {}

II. initialization from a braced-init-list (trivial, one use case)

While reading the old P0144R0 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0144r0.pdf) the section 3.2 caught my attention.
3.2 Should this syntax support initialization from a braced-init-list?

For example:
auto {x,y,z} = {1, xyzzys, 3.14159}; // NOT proposed We think the answer should be no.

This would be trivial to add, but should be well motivated and we know of no use cases where this offers additional expressive power not already available (and with greater clarity) using individual variable declarations."

I think I've found a use case. Not a big one, but still
for (auto[x, y, z] = {1, "xyzzy"s, 3.14159}; ...; ...) {}

Which would be the same as:
for (auto[x, y, z] = std::tuple{1, "xyzzy"s, 3.14159}; ...; ...) {}

But without having to use std::tuple.

No.

First, the only reason to want this is to declare multiple variables of different types in places where we currently only allow declaring variables of one type. And generally speaking, places where you're doing that have serious constraints on what you can do with those variables.

We really don't need to see bloated `for` and `if` statements.

Second, if we're going to allow this, then it should not involve `std::tuple`. We would simply declare that a structured binding statement initialized with a braced-init-list simply creates multiple variables with types deduced from the appropriate locations in the braced-init-list. We should not needlessly associate a language feature like this with a library type.


III. Parameter pack through structured binding (controversial?, useful, complex to implement?)

No, we have other proposals dealing with packs.

Daemon Snake

unread,
Jul 20, 2017, 9:26:31 PM7/20/17
to ISO C++ Standard - Future Proposals
Le vendredi 21 juillet 2017 02:20:00 UTC+2, Nicol Bolas a écrit :
Second, if we're going to allow this, then it should not involve `std::tuple`. We would simply declare that a structured binding statement initialized with a braced-init-list simply creates multiple variables with types deduced from the appropriate locations in the braced-init-list. We should not needlessly associate a language feature like this with a library type.

This compiles, now. It's valid C++.
for (auto[x, y, z] = std::tuple{1, "xyzzy"s, 3.14159}; ...; ...) {}

This does not:

Daemon Snake

unread,
Jul 20, 2017, 10:01:27 PM7/20/17
to ISO C++ Standard - Future Proposals
Le vendredi 21 juillet 2017 02:20:00 UTC+2, Nicol Bolas a écrit :
No, we have other proposals dealing with packs.

     The last one to have a similar impact on the language was P0341R0 in 2016 and was not well received (https://groups.google.com/a/isocpp.org/forum/#!topic/reflection/KWotU1R0UCI),
introduced a lot of grammar changes, new operators, "...x..." syntax, etc...
     The auto[x...] idea would allow more generic structured binding and by side effet would allow local variable packs with one grammatical change in an isolated context.

Nicol Bolas

unread,
Jul 20, 2017, 11:56:50 PM7/20/17
to ISO C++ Standard - Future Proposals


On Thursday, July 20, 2017 at 9:26:31 PM UTC-4, Daemon Snake wrote:
Le vendredi 21 juillet 2017 02:20:00 UTC+2, Nicol Bolas a écrit :
Second, if we're going to allow this, then it should not involve `std::tuple`. We would simply declare that a structured binding statement initialized with a braced-init-list simply creates multiple variables with types deduced from the appropriate locations in the braced-init-list. We should not needlessly associate a language feature like this with a library type.
This compiles, now. It's valid C++.
for (auto[x, y, z] = std::tuple{1, "xyzzy"s, 3.14159}; ...; ...) {}

This does not:
for (auto[x, y, z] = {1, "xyzzy"s, 3.14159}; ...; ...) {}

Yes, I'm aware of that. I'm not really sure what that's supposed to prove though.

My point is that if you're making a language feature, it should not be a simple shortcut to library stuff if there's a way to do better. And there's no reason to have an intermediary `tuple`, so there is a way to do better.


On Thursday, July 20, 2017 at 10:01:27 PM UTC-4, Daemon Snake wrote:
Le vendredi 21 juillet 2017 02:20:00 UTC+2, Nicol Bolas a écrit :
No, we have other proposals dealing with packs.

     The last one to have a similar impact on the language was P0341R0 in 2016 and was not well received (https://groups.google.com/a/isocpp.org/forum/#!topic/reflection/KWotU1R0UCI),

Nonsense. P0535 deals with something very similar, and the committee didn't seem to dislike it. It doesn't let you name packs, but you can use any type that can undergo structured binding as though it were a pack. Which is the same thing, only far more powerful.

Matthew Woehlke

unread,
Jul 28, 2017, 3:31:20 PM7/28/17
to std-pr...@isocpp.org, Daemon Snake
On 2017-07-20 19:37, Daemon Snake wrote:
> *III. Parameter pack through structured binding *(controversial?, useful,
> complex to implement?)
>
> auto[x...] = something;

This was specifically mentioned in http://wg21.link/p0535. The short
version is, I'd rather have generalized unpacking; it tends to cover the
same use cases but without introducing an extra declaration.

> auto[values...] = func(...);
> print(values...);

auto values = func();
print([:]values...);

> template <class Tuple>
> constexpr auto reverse_tuple(Tuple&& tuple)

This example is also shown in P0535, albeit expressed differently. I
believe P0535 would also allow an expression nearly identical to yours,
however.

> Disclaimer: It would also require something like nested structured binding
> to be really useful with user types

Nested structured bindings would be nice, but they are syntactically
hard (due to ambiguities with attributes IIRC). There was talk a while
back about allowing nested bindings, and also allowing empty bindings:

auto [] = acquire_lock(...);

(Hey, look; an anonymous variable!)

--
Matthew

Daemon Snake

unread,
Jul 28, 2017, 6:22:26 PM7/28/17
to ISO C++ Standard - Future Proposals, swa...@gmail.com
I Agree.

I only proposed III because I didn't know about P0535.
It's a way more interesting proposal and if I had to choose between the 2, I would choose P0535.

As for anonymous variables I'm not sure that structured binding would be the best way to achieve it.
auto [] = acquire_lock(); //not really self evident
auto = acquire_lock();//simpler

A good reason for it tough would be type specification which would be quite useful for declarative coding.
I'm not quite sure the kind of syntax we would need to use though.
int fd = ...;
scope_exit
[] = []() { close(fd); };
...
func_that_may_throw
();
...

Matthew Woehlke

unread,
Jul 31, 2017, 11:20:15 AM7/31/17
to std-pr...@isocpp.org, Daemon Snake
On 2017-07-28 18:22, Daemon Snake wrote:
> As for anonymous variables I'm not sure that structured binding would be
> the best way to achieve it.
> auto [] = acquire_lock(); //not really self evident
> auto = acquire_lock();//simpler

I understand that, but I believe that both have been suggested and
neither has gained traction. The latter has possible grammar issues that
the former can dodge. Also, the former, assuming we get nesting, will
also allow things like:

auto [a, [], b] = some_3tuple(); // don't care about second part

...which is something else that seems wanted.

> I'm not quite sure the kind of syntax we would need to use though.
> scope_exit [] = []() { close(fd); };

auto [] = scope_exit{[]() { ... }};

The type doesn't need to be on the LHS.

--
Matthew

Ray Hamel

unread,
Jul 31, 2017, 4:29:42 PM7/31/17
to ISO C++ Standard - Future Proposals, swa...@gmail.com
What about overloading the `void` keyword for this purpose? IMO it makes the intent quite a bit more obvious than empty square brackets do.

auto [a, void, c] = some_3tuple(); // don't care about second part

-Ray

Tony V E

unread,
Jul 31, 2017, 8:37:51 PM7/31/17
to Standard Proposals
On Mon, Jul 31, 2017 at 4:29 PM, Ray Hamel <rayg...@gmail.com> wrote:
What about overloading the `void` keyword for this purpose? IMO it makes the intent quite a bit more obvious than empty square brackets do.

auto [a, void, c] = some_3tuple(); // don't care about second part

-Ray


We can make _ work, and be backwards compatible with current usage:

auto [a, _, c] = some_3tuple(); // don't care about second part

int x = _; // ok, whatever, I guess it is a normal variable (backwards compat!)

lock_guard _ = ...; // sure reuse _ - it is now anonymous

auto copy = _; // error: _ because anonymous, but was reused


Or make the first use (int x = _;) imply that it cannot become anonymous:

// scenario 1
int _ = f(); // might be anon?
int x = _; // OK, guess not
int _ = g(); // error: _ is not anon, can't be reused

// scenario 2 (in some other scope)
int _ = f(); // might be anon
int _ = g(); // OK, definitely anonymous
int _ = h(); // sure
int x = _; // error: _ was anonymous, ambiguous use

Tony


 
On Monday, July 31, 2017 at 11:20:15 AM UTC-4, Matthew Woehlke wrote:
On 2017-07-28 18:22, Daemon Snake wrote:
> As for anonymous variables I'm not sure that structured binding would be
> the best way to achieve it.
> auto [] = acquire_lock(); //not really self evident
> auto = acquire_lock();//simpler

I understand that, but I believe that both have been suggested and
neither has gained traction. The latter has possible grammar issues that
the former can dodge. Also, the former, assuming we get nesting, will
also allow things like:

  auto [a, [], b] = some_3tuple(); // don't care about second part

...which is something else that seems wanted.

> I'm not quite sure the kind of syntax we would need to use though.
> scope_exit [] = []() { close(fd); };

  auto [] = scope_exit{[]() { ... }};

The type doesn't need to be on the LHS.

--
Matthew

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/6592c3d5-9227-4964-9580-9a922b6f7c60%40isocpp.org.



--
Be seeing you,
Tony

Ville Voutilainen

unread,
Jul 31, 2017, 8:49:18 PM7/31/17
to ISO C++ Standard - Future Proposals
On 1 August 2017 at 03:37, Tony V E <tvan...@gmail.com> wrote:
> We can make _ work, and be backwards compatible with current usage:
>
> auto [a, _, c] = some_3tuple(); // don't care about second part
>
> int x = _; // ok, whatever, I guess it is a normal variable (backwards
> compat!)
>
> lock_guard _ = ...; // sure reuse _ - it is now anonymous

How is that lock_guard definition backwards compatible?

> auto copy = _; // error: _ because anonymous, but was reused

How is that backwards compatible?

Patrice Roy

unread,
Jul 31, 2017, 9:52:51 PM7/31/17
to std-pr...@isocpp.org
Most of my lock_guards are called _ today, as there's no point in using them (explicitly) after declaration. I hope any change we make that uses _ does not prevent this very common use-case. From what I can see, the suggestions floated here would not prove to be a problem, even if _ was reused for other, not-anonymous-but-not-usable-except-for-implicit-destruction cases, and I'm fine with them as long as this situation persists :)

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Daemon Snake

unread,
Jul 31, 2017, 9:53:48 PM7/31/17
to ISO C++ Standard - Future Proposals
Le mardi 1 août 2017 02:37:51 UTC+2, Tony V E a écrit :
We can make _ work, and be backwards compatible with current usage:

That could be quite dangerous as '_' is a legal C++ identifier.
One of the focus of the standard is still to maintain legacy code.

Imagine this:
some_type _;

void func()
{
   
int _ = ...; //is now anonymous by side effect
}

Ray's proposal has at least clearness on its side but clashes with the "regular void" proposal.

What about this ?
//making name optional for C-style struct/class variable declaration only if initialized
//anonymous variables are only useful for struct/class types anyway
class type_name = ...; //declares an anonymous variable of type type_name
class auto = ...; //auto is a reserved keyword so why not

//Impact on the syntax ?
class double =...; //no changes, illegal double is not a struct/class
class type_name; //no changes, legal, declares a struct named t
class type_name id; //no changes, legal.
class type_name id = ...; //no changes, legal.

Daemon Snake

unread,
Jul 31, 2017, 10:14:11 PM7/31/17
to ISO C++ Standard - Future Proposals
One thing I've forgot though is that aliases and template deduction are not currently allowed in C-style struct variable declaration.
That might be one thing that might be interesting to change also.

Tony V E

unread,
Aug 1, 2017, 7:28:52 AM8/1/17
to Standard Proposals
On Mon, Jul 31, 2017 at 8:49 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 1 August 2017 at 03:37, Tony V E <tvan...@gmail.com> wrote:
> We can make _ work, and be backwards compatible with current usage:
>
> auto [a, _, c] = some_3tuple(); // don't care about second part
>
> int x = _; // ok, whatever, I guess it is a normal variable (backwards
> compat!)
>
> lock_guard _ = ...; // sure reuse _ - it is now anonymous

How is that lock_guard definition backwards compatible?

Currently reusing a variable name in the same scope is an error, so I'm saying - for _ only - don't make it an error. Make it mean "_ is now anonymous for the rest of this scope"

 

> auto copy = _; // error: _ because anonymous, but was reused

How is that backwards compatible?


You currently can't have 2 _ variables in the same scope. That last line is in the context of "doesn't compile today".


// 1. normal. today.
{
    int _ = 17;
    int x = _ * _;
}

// 2.
{
    int _ = 17;
    int _ = 24;  // error today, make OK, anonymous in proposal
    int x = _ * _; // proposal - error: ambiguous *use* of _
}

ie you can *declare* as many _ variables as you want in a scope, but if you have more than 1, you can't *use* the variable in an expression (because it would be obviously ambiguous as to which _ you were using).


There is only one design question - this case:
// 3.
{
   int _ = 17;
   int x = _ * _;  // *use* _, "normal" so far

   // should the next line be an error? (as _ was already *used*)
   // or does this just make it anonymous starting here:
   int _ = 24;

   int x = _ * _; // definitely error - ambiguous
}

I could go either way on that last decision.

Tony V E

unread,
Aug 1, 2017, 7:29:46 AM8/1/17
to Standard Proposals

On Mon, Jul 31, 2017 at 9:53 PM, Daemon Snake <swa...@gmail.com> wrote:
Le mardi 1 août 2017 02:37:51 UTC+2, Tony V E a écrit :
We can make _ work, and be backwards compatible with current usage:

That could be quite dangerous as '_' is a legal C++ identifier.
One of the focus of the standard is still to maintain legacy code.

Imagine this:
some_type _;

void func()
{
   
int _ = ...; //is now anonymous by side effect
}


No, that would be normal code, as is today - we allow _ to be used once per scope today.

I'm suggesting _ becomes anonymous *within a scope* when it is used to declare more than one variable *within a scope*.

Ville Voutilainen

unread,
Aug 1, 2017, 7:48:26 AM8/1/17
to ISO C++ Standard - Future Proposals
On 1 August 2017 at 14:28, Tony V E <tvan...@gmail.com> wrote:
>> > We can make _ work, and be backwards compatible with current usage:
>> >
>> > auto [a, _, c] = some_3tuple(); // don't care about second part
>> >
>> > int x = _; // ok, whatever, I guess it is a normal variable (backwards
>> > compat!)
>> >
>> > lock_guard _ = ...; // sure reuse _ - it is now anonymous
>>
>> How is that lock_guard definition backwards compatible?
>
>
> Currently reusing a variable name in the same scope is an error, so I'm
> saying - for _ only - don't make it an error. Make it mean "_ is now
> anonymous for the rest of this scope"

Woo hoo, context-specific semantics that silently change depending on
prior declarations
or lack of them in the same scope. NO.

>> > auto copy = _; // error: _ because anonymous, but was reused
>>
>> How is that backwards compatible?
>>
>
> You currently can't have 2 _ variables in the same scope. That last line is
> in the context of "doesn't compile today".

auto _ = whatever;
auto copy = _;

compiles fine, today.

Matthew Woehlke

unread,
Aug 1, 2017, 1:44:39 PM8/1/17
to std-pr...@isocpp.org, Ray Hamel, swa...@gmail.com
On 2017-07-31 16:29, Ray Hamel wrote:
> What about overloading the `void` keyword for this purpose? IMO it makes
> the intent quite a bit more obvious than empty square brackets do.
>
> auto [a, void, c] = some_3tuple(); // don't care about second part

The main attraction for using `[]` is that it gives us anonymous
variables with only a slight tweak to SB.

That said... has anyone suggested using `void` as an anonymous variable
name before? What's wrong with it?

auto void = get_scoped_thingy();
lock_guard void = some_mutex;
auto [a, void, b, void] = tuple; // optional bonus

--
Matthew

Ville Voutilainen

unread,
Aug 1, 2017, 1:50:03 PM8/1/17
to ISO C++ Standard - Future Proposals, Ray Hamel, swa...@gmail.com
On 1 August 2017 at 20:44, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
> That said... has anyone suggested using `void` as an anonymous variable
> name before? What's wrong with it?

It's a type, not an identifier.

> auto void = get_scoped_thingy();
> lock_guard void = some_mutex;
> auto [a, void, b, void] = tuple; // optional bonus

Sure, that would work. It seems like shoehorning new special semantics
into void.
I'd rather seek some other solution.

Vicente J. Botet Escriba

unread,
Aug 2, 2017, 7:13:42 AM8/2/17
to std-pr...@isocpp.org, Ville Voutilainen

What about using __ instead. This is more constrained as used only by the standard library. The compiler should signal an error when used in other contexts.

Vicente

Tony V E

unread,
Aug 3, 2017, 12:02:25 AM8/3/17
to Standard Proposals
On Tue, Aug 1, 2017 at 7:48 AM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 1 August 2017 at 14:28, Tony V E <tvan...@gmail.com> wrote:
>> > We can make _ work, and be backwards compatible with current usage:
>> >
>> > auto [a, _, c] = some_3tuple(); // don't care about second part
>> >
>> > int x = _; // ok, whatever, I guess it is a normal variable (backwards
>> > compat!)
>> >
>> > lock_guard _ = ...; // sure reuse _ - it is now anonymous
>>
>> How is that lock_guard definition backwards compatible?
>
>
> Currently reusing a variable name in the same scope is an error, so I'm
> saying - for _ only - don't make it an error. Make it mean "_ is now
> anonymous for the rest of this scope"

Woo hoo, context-specific semantics that silently change depending on
prior declarations
or lack of them in the same scope. NO.

I'm not sure I'd use the words "semantics" - it becomes an error.  Just like "int x" is an error depending on prior declarations - ie if x was already declared.
Runtime behaviour doesn't change - it either compiles or doesn't.

#if FOO
int x = 17;
#endif
int x = 21; // Woo hoo "semantics" (compile or not) depends on prior declarations

#if FOO
int _ = 17;
#endif
int _ = 18; // this is immune to prior declarations! better than x!
return _ * _; // Woo hoo "semantics" (compile or not) depends on prior declarations




 

>> > auto copy = _; // error: _ because anonymous, but was reused
>>
>> How is that backwards compatible?
>>
>
> You currently can't have 2 _ variables in the same scope. That last line is
> in the context of "doesn't compile today".

auto _ = whatever;
auto copy = _;

compiles fine, today.


Sure.  My example was in this context:


int _ = f(); // might be anon
int _ = g(); // OK, definitely anonymous
int _ = h(); // sure
int x = _; // error: _ was anonymous, ambiguous us

There is no need/possibility for backwards compatibility here.  The three _ declarations already mean we aren't in Kansas anymore.

Maybe I'm not explaining this well?

Also, I'm not saying it is the best idea in the world, just that it is possible to use _ as an anonymous variable in a way that is backwards compatible.
So step 1 is agree or disagree on whether it really is BC.
Step 2 can be saying you just don't like it, even just because "_ shouldn't be special".

Vicente J. Botet Escriba

unread,
Aug 3, 2017, 3:33:52 AM8/3/17
to std-pr...@isocpp.org, Tony V E
To be sure I understand. _ becomes anonymous once there are two different declarations named _.
From one side I like it because it allows some kind of anomymous variables, however this means that the reader don't know that _ is anonymous until the whole scope is inspected.
If we restrict __ as I proposed we don't have this downside. Onely the standard library authors need to change any non-anonymous use of __.

Vicente

Ville Voutilainen

unread,
Aug 3, 2017, 6:39:00 AM8/3/17
to ISO C++ Standard - Future Proposals
On 3 August 2017 at 07:02, Tony V E <tvan...@gmail.com> wrote:
>> > Currently reusing a variable name in the same scope is an error, so I'm
>> > saying - for _ only - don't make it an error. Make it mean "_ is now
>> > anonymous for the rest of this scope"
>>
>> Woo hoo, context-specific semantics that silently change depending on
>> prior declarations
>> or lack of them in the same scope. NO.
>
>
> I'm not sure I'd use the words "semantics" - it becomes an error. Just like

Yes, it conditionally becomes an error. Sometimes it doesn't. That is
not a sane solution
for programmers, even if we would be willing to stomach such a hack.
And an additional
part of the problem is this:

Handle _ = something();
// let's not actually use it much anywhere

Handle _ = something();
// now let's start using it:
f(_);

All good so far. Now, a tad differently:

Handle _ = something();
Handle _ = something_else();

"Great", still works. Now we add the call,

Handle _ = something();
Handle _ = something_else();
f(_);

and the program is ill-formed. Explaining that would be hard.

Find another solution.

Tony V E

unread,
Aug 3, 2017, 9:21:45 PM8/3/17
to Standard Proposals
I actually don't think it would be that hard.  I would probably *eventually* make using _ as a variable (ie non-anonymous) deprecated.
ie teach it as only use it as anonymous (which is probably the typical case already), and other usage is just there for old-times-sake.

But I see your point.


I don't like

    auto [] = foo();

because that might lead to

    auto [x,y] = Point3D(1,2,3); // drop Z

and then you lose the chance of catching an error there.
(In fact auto [] = foo(); should maybe imply foo() returns void)
I think

    auto [x,y,...] = Point3D(1,2,3);

is more clear.
That leads to

auto [...] = foo(); // anonymous variable


Vicente J. Botet Escriba

unread,
Aug 4, 2017, 2:29:01 AM8/4/17
to std-pr...@isocpp.org, Tony V E
Only if foo() returns a product-type. In order to make this a real anonymous variable we need to extend structure binding to support any type that is not a product type now as a product type of one element or add a specific grammar for the case [...].

I really would prefer the use of '__' (two '_') as anonymous variable/name and deprecate it as a valid variable as you suggested for '_'.
Are there any strong arguments against this?

Vicente

Ville Voutilainen

unread,
Aug 4, 2017, 3:21:43 AM8/4/17
to ISO C++ Standard - Future Proposals
On 4 August 2017 at 04:21, Tony V E <tvan...@gmail.com> wrote:
>> and the program is ill-formed. Explaining that would be hard.
>>
>> Find another solution.
>>
>
>
> I actually don't think it would be that hard. I would probably *eventually*

It involves answering questions like "what kind of idiots are you
people who design this language?".
That's the hard part, especially trying to explain that they aren't
when the person doing the
explaining can't do so honestly.

Tom Honermann

unread,
Aug 4, 2017, 12:56:33 PM8/4/17
to std-pr...@isocpp.org
On 08/03/2017 09:21 PM, Tony V E wrote:

I don't like

    auto [] = foo();

because that might lead to

    auto [x,y] = Point3D(1,2,3); // drop Z

and then you lose the chance of catching an error there.

I've heard this argument before, but I don't really understand it.  If the user needs the undeclared binding, I would expect them to pretty quickly notice its absence.  If the undeclared binding isn't needed, then why should the user be forced to name it?  I appreciate that, depending on how the feature is defined, there could be an observable difference when binding tuple-like types if calls to get<>() were elided for undeclared bindings (assuming that the call to get<>() had side effects), but such eliding seems like it would be desirable in the general case.

Tom.

Nicol Bolas

unread,
Aug 4, 2017, 1:22:16 PM8/4/17
to ISO C++ Standard - Future Proposals

What if the user actually meant to call something different?

auto [x, y, z] = some_func(...);
vs
auto [x, y] = some_func(...);

Whether the user uses `z` is not relevant. What matters most is that the user called a function that returns 3 values. If the latter compiles, then there is an inconsistency between what the user specified (2 values) and what the API specified (3 values).

Such inconsistencies are usually considered compile errors. They certainly are when you call a function that takes 3 parameters, but you only provide 2. Why should return values be any different?

Tom Honermann

unread,
Aug 4, 2017, 6:04:05 PM8/4/17
to std-pr...@isocpp.org
Well, it isn't an error to call a function that takes 3 arguments when only 2 are provided if the function declaration supplies a default argument for the 3rd.

I don't find this argument compelling.  There is only one return type and the return type does not influence overload resolution.  To me, this sounds like an argument against use of the 'auto' type specifier at all - what if the user meant to call a function returning some class type instead of std::tuple?  The ability to omit declarations for unwanted bindings seems more useful to me than the opportunity to catch cases where an unintended function was called.

Tom.

Nicol Bolas

unread,
Aug 4, 2017, 11:31:58 PM8/4/17
to ISO C++ Standard - Future Proposals
On Friday, August 4, 2017 at 6:04:05 PM UTC-4, Tom Honermann wrote:
On 08/04/2017 01:22 PM, Nicol Bolas wrote:
On Friday, August 4, 2017 at 12:56:33 PM UTC-4, Tom Honermann wrote:
On 08/03/2017 09:21 PM, Tony V E wrote:

I don't like

    auto [] = foo();

because that might lead to

    auto [x,y] = Point3D(1,2,3); // drop Z

and then you lose the chance of catching an error there.

I've heard this argument before, but I don't really understand it.  If the user needs the undeclared binding, I would expect them to pretty quickly notice its absence.  If the undeclared binding isn't needed, then why should the user be forced to name it?  I appreciate that, depending on how the feature is defined, there could be an observable difference when binding tuple-like types if calls to get<>() were elided for undeclared bindings (assuming that the call to get<>() had side effects), but such eliding seems like it would be desirable in the general case.

What if the user actually meant to call something different?

auto [x, y, z] = some_func(...);
vs
auto [x, y] = some_func(...);

Whether the user uses `z` is not relevant. What matters most is that the user called a function that returns 3 values. If the latter compiles, then there is an inconsistency between what the user specified (2 values) and what the API specified (3 values).

Such inconsistencies are usually considered compile errors. They certainly are when you call a function that takes 3 parameters, but you only provide 2. Why should return values be any different?

Well, it isn't an error to call a function that takes 3 arguments when only 2 are provided if the function declaration supplies a default argument for the 3rd.

Which requires explicit syntax to be used on the function declaration. By your logic, to have a function return 3 values, but the caller only take two should also require explicit syntax in the function declaration.

I don't find this argument compelling.  There is only one return type and the return type does not influence overload resolution. To me, this sounds like an argument against use of the 'auto' type specifier at all -

... how?

My argument is that returning multiple values is conceptually identical to passing multiple values. Therefore, if the compiler gives an error when you pass 2 values to a function that takes 3, then by analogy, a function which returns 3 values should give an error if you only accept 2.

This is not about which overload you take or that you're using `auto` or anything of that sort.
 
what if the user meant to call a function returning some class type instead of std::tuple?

I'm gonna ignore the fact that `std::tuple` is very much a class type.

By using `auto []`, you're conceptually saying that you don't care what actual type a function returns. What you care about is that it returns a type which is decomposable into a number of values, and it is those values that you want access to.

So by saying `auto [a, b] = foo(c, d)`, you're creating a contract that checks two things:

1) `foo` is a callable-thing that can take two values as arguments.
2) `foo`, when taking 2 values, returns a type that is decomposable into 2 values.

Note that this is not "decomposable into at least 2 values". Just like `foo` is not "a callable-thing that can take at least two values as arguments".

If you want to play your default-arguments game, then we should have some decomposable machinery that explicitly has the type advertise that one or more of the values can be ignored. But this would be an innate part of the type itself.
 
The ability to omit declarations for unwanted bindings seems more useful to me than the opportunity to catch cases where an unintended function was called.

I'm more concerned about why you're omitting values so frequently from a multi-value return.

Ricardo Fabiano de Andrade

unread,
Aug 5, 2017, 2:21:33 PM8/5/17
to std-pr...@isocpp.org
What about:

auto [...] = product_type;
auto ... = any_type;

Literally, "..." following a type and preceding an assignment would mean an anonymous variable.
As well as within structured binding, as suggested previously.

Just bickesheeding, not aware of possible implementation challenges. :)



--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Tom Honermann

unread,
Aug 6, 2017, 7:44:37 PM8/6/17
to std-pr...@isocpp.org
On 08/04/2017 11:31 PM, Nicol Bolas wrote:
On Friday, August 4, 2017 at 6:04:05 PM UTC-4, Tom Honermann wrote:
On 08/04/2017 01:22 PM, Nicol Bolas wrote:
On Friday, August 4, 2017 at 12:56:33 PM UTC-4, Tom Honermann wrote:
On 08/03/2017 09:21 PM, Tony V E wrote:

I don't like

    auto [] = foo();

because that might lead to

    auto [x,y] = Point3D(1,2,3); // drop Z

and then you lose the chance of catching an error there.

I've heard this argument before, but I don't really understand it.  If the user needs the undeclared binding, I would expect them to pretty quickly notice its absence.  If the undeclared binding isn't needed, then why should the user be forced to name it?  I appreciate that, depending on how the feature is defined, there could be an observable difference when binding tuple-like types if calls to get<>() were elided for undeclared bindings (assuming that the call to get<>() had side effects), but such eliding seems like it would be desirable in the general case.

What if the user actually meant to call something different?

auto [x, y, z] = some_func(...);
vs
auto [x, y] = some_func(...);

Whether the user uses `z` is not relevant. What matters most is that the user called a function that returns 3 values. If the latter compiles, then there is an inconsistency between what the user specified (2 values) and what the API specified (3 values).

Such inconsistencies are usually considered compile errors. They certainly are when you call a function that takes 3 parameters, but you only provide 2. Why should return values be any different?

Well, it isn't an error to call a function that takes 3 arguments when only 2 are provided if the function declaration supplies a default argument for the 3rd.

Which requires explicit syntax to be used on the function declaration. By your logic, to have a function return 3 values, but the caller only take two should also require explicit syntax in the function declaration.

It was you that made the comparison between decomposing the return type and function arguments, not me.  I was just pointing out that your argument was incomplete; that arguments need not be provided if default arguments are available.



I don't find this argument compelling.  There is only one return type and the return type does not influence overload resolution. To me, this sounds like an argument against use of the 'auto' type specifier at all -

... how?

In the absence of decomposition, 'auto' imposes no constraints on the return type.  You're arguing that, in the presence of decomposition, constraints be added that exceed the minimum constraints necessary to satisfy the binding list.  To me, that sounds similar to concerns I've heard that using 'auto' removes convertibility constraints that would otherwise be checked.



My argument is that returning multiple values is conceptually identical to passing multiple values. Therefore, if the compiler gives an error when you pass 2 values to a function that takes 3, then by analogy, a function which returns 3 values should give an error if you only accept 2.

I understand the argument, I just don't agree with it.  Again, contrasting with default arguments, if a function can supply default arguments for arguments not explicitly provided, why can a caller not omit return values that it does not require?



This is not about which overload you take or that you're using `auto` or anything of that sort.

That seems to be what your argument is though; that the binding list be employed to diagnose potential issues with call resolution (including overload resolution) beyond that which is strictly necessary.


 
what if the user meant to call a function returning some class type instead of std::tuple?

I'm gonna ignore the fact that `std::tuple` is very much a class type.

I thought the distinction was clear enough; decomposition works differently for std::tuple vs non-customized class types.



By using `auto []`, you're conceptually saying that you don't care what actual type a function returns. What you care about is that it returns a type which is decomposable into a number of values, and it is those values that you want access to.

Yes, that I agree with.



So by saying `auto [a, b] = foo(c, d)`, you're creating a contract that checks two things:

1) `foo` is a callable-thing that can take two values as arguments.
2) `foo`, when taking 2 values, returns a type that is decomposable into 2 values.

I don't agree with the 2nd part.  Or, rather, I agree there is more than one rational behavior.  I see there being a minimal constraint that the return type be decomposable into *at least* 2 values.  Constraining the decomposition to exactly 2 values goes beyond what is minimally necessary.  In my view, requiring the arity to match is over constraining.



Note that this is not "decomposable into at least 2 values". Just like `foo` is not "a callable-thing that can take at least two values as arguments".

'foo' may be an invokable that takes more than two values if it defines default arguments or is variadic.



If you want to play your default-arguments game, then we should have some decomposable machinery that explicitly has the type advertise that one or more of the values can be ignored. But this would be an innate part of the type itself.

I don't see motivation for that.  Nor would I want that to be part of the type.  It might be reasonable to impose a same-arity constraint if the function is declared with [[nodiscard]] though.


 
The ability to omit declarations for unwanted bindings seems more useful to me than the opportunity to catch cases where an unintended function was called.

I'm more concerned about why you're omitting values so frequently from a multi-value return.

Consider insertion into a container where one is unconcerned with whether the insertion was successful.  I'd rather not have to name the 2nd binding.

void f(my_set s, my_value v) {
  auto [it] = s.insert(v);
}

That is just one example of a class of functions that returns values that are not always applicable to the caller.  There are many such functions.

Tom.

Tom Honermann

unread,
Aug 6, 2017, 8:08:33 PM8/6/17
to std-pr...@isocpp.org
On 08/03/2017 09:21 PM, Tony V E wrote:

I don't like

    auto [] = foo();

because that might lead to

    auto [x,y] = Point3D(1,2,3); // drop Z

and then you lose the chance of catching an error there.
(In fact auto [] = foo(); should maybe imply foo() returns void)

There is an additional trade off here.  Since callers are currently required to name each binding, compilers can't (reasonably) warn about unused bindings.  P0609R0 [1] proposed allowing attributes in binding declarations and that would have enabled specifying [[maybe_unused]] for particular cases, but EWG declined to move that forward in Toronto.  The ability to elide names for unneeded bindings would allow compilers to issue warnings:

void f() {
  auto [w, [], y] = get_wxyz(); // ignore x and z.
  w;
} // warn that y is unused.

Tom.

[1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0609r0.pdf

Tony V E

unread,
Aug 6, 2017, 8:41:12 PM8/6/17
to Standard Proposals
Speaking of w,

// thing.h
Point3D getPoint();

// user.cpp
auto [x,y] = getPoint(); // just want x,y, don't care about z

....
later

// thing.h
Point4D getPoint(); // update: now also returns w

// user.cpp
auto [x,y] = getPoint(); // just want x, y, but I got w,x


As a library writer, I've lost some ability to change API, because what was a compile time error is now a run time error.

Not sure if that's a big issue, but it does open up a new wrinkle.

Yes, I also find this similar to 'auto' vs constrained (and I prefer constrained)

auto foo = getFoo();
auto total = foo + bar;

Did you mean that to work with strings, or just Numbers?  Is it OK if '/' means concatenate (for paths)? (of course I doubt getFoo() will change one day from Number to path)

Just like Concepts expose the requirements on types for templates,
Concepts can also expose the requirements on types for the code that follows the declaration.
Now, I doubt auto is as problematic as T is in unconstrained templates, but it is basically the same thing.

Tom Honermann

unread,
Aug 6, 2017, 10:48:17 PM8/6/17
to std-pr...@isocpp.org
That's certainly fair.  I'm likewise unsure how big a deal that is.  My gut tells me that, in most cases, a change in which value a declaration binds to will also result in a change in type that is likely to produce later errors; though that is unlikely to be the case with the example above.  Regardless, the ability to change API in this way depends on the ability to inflict compile time errors on callers and therefore already has limits.



Yes, I also find this similar to 'auto' vs constrained (and I prefer constrained)

auto foo = getFoo();
auto total = foo + bar;

Did you mean that to work with strings, or just Numbers?  Is it OK if '/' means concatenate (for paths)? (of course I doubt getFoo() will change one day from Number to path)

Just like Concepts expose the requirements on types for templates,
Concepts can also expose the requirements on types for the code that follows the declaration.
Now, I doubt auto is as problematic as T is in unconstrained templates, but it is basically the same thing.

Yup.  I generally restrict my use of auto to cases where the deduced type is determined by an expression involving types that I've already constrained.

Tom.

Matthew Woehlke

unread,
Aug 7, 2017, 9:53:51 AM8/7/17
to std-pr...@isocpp.org, Ricardo Fabiano de Andrade
On 2017-08-05 14:21, Ricardo Fabiano de Andrade wrote:
> What about:
>
> auto [...] = product_type;
> auto ... = any_type;
>
> Literally, "..." following a type and preceding an assignment would mean an
> anonymous variable.
> As well as within structured binding, as suggested previously.
>
> Just bickesheeding, not aware of possible implementation challenges. :)

I think this would be confusing with people expecting it to have some
relation to parameter packs. I think just `.` with otherwise the same
semantics has been suggested; I'd be in favor of that. Or possibly `..`.

--
Matthew
Reply all
Reply to author
Forward
0 new messages