Structured Bindings - revisited

329 views
Skip to first unread message

Richard Hodges

unread,
Dec 13, 2017, 5:15:56 AM12/13/17
to ISO C++ Standard - Future Proposals
A simple example:

Here's a fairly common idiom, expressed in terms of a structured binding:

#include <iostream>
#include <vector>
#include <tuple>

int main()
{
    std::vector<int> v = { 6,5,4,3,2,1 };

    for( auto [i, first, last] = std::make_tuple(std::size_t(0), begin(v), end(v))
       ; first != last
       ; ++i, ++first)
    {
        std::cout << "index: " << i << "\t" << *first << "\n";
    }
}

It seems sensible to me to group the loop's variables in a structured binding since 

  • it's succinct and,
  • they remain firmly local to the loop's scope
  • It's the only way to declare multiple dissimilar variables within the for statement.

However, this code is not DRY - the naming of the bound variables must correspond to the creation of the tuple. Maintenance confusion awaits.

It seems to me that a small, non-breaking syntax change could allow this:

    for( auto [i = size_t(0), first = begin(v), last = end(v)]
       ; first != last
       ; ++i, ++first)
    {
        // ...
    }

which would:

a) be 100% DRY,
b) involve less typing
c) match the syntax for lambda capture (I could expand further on my thoughts on that, but perhaps another day)
d) IMHO be easier to teach

Wouldn't that be better?

I'd value your thoughts.


Giovanni Piero Deretta

unread,
Dec 13, 2017, 5:21:00 AM12/13/17
to ISO C++ Standard - Future Proposals

Or just allow auto to infer different values for each variable.
 

Jake Arkinstall

unread,
Dec 13, 2017, 7:49:20 AM12/13/17
to std-pr...@isocpp.org
Or just allow auto to infer different values for each variable.

That's a good point. Can anyone see a reason that auto should not allow a list of variables to have different type?

--
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/53a10b81-687a-40de-8420-b439d394af1a%40isocpp.org.

Nicol Bolas

unread,
Dec 13, 2017, 10:26:02 AM12/13/17
to ISO C++ Standard - Future Proposals
On Wednesday, December 13, 2017 at 5:15:56 AM UTC-5, Richard Hodges wrote:
A simple example:

Here's a fairly common idiom, expressed in terms of a structured binding:

I'm sure some people do this, but an indexed range view would handle this much more easily and compactly:

for(auto &[i, val] : std::counted_view(rng))
{...}

#include <iostream>
#include <vector>
#include <tuple>

int main()
{
    std::vector<int> v = { 6,5,4,3,2,1 };

    for( auto [i, first, last] = std::make_tuple(std::size_t(0), begin(v), end(v))
       ; first != last
       ; ++i, ++first)
    {
        std::cout << "index: " << i << "\t" << *first << "\n";
    }
}

It seems sensible to me to group the loop's variables in a structured binding since 

  • it's succinct and,
  • they remain firmly local to the loop's scope
  • It's the only way to declare multiple dissimilar variables within the for statement.

However, this code is not DRY - the naming of the bound variables must correspond to the creation of the tuple. Maintenance confusion awaits.

Perhaps you're mistaking DRY for some other principle. DRY is Don't Repeat Yourself. This code involves no repetition of typenames, variable names, objects, literals, or anything else. Nothing is being repeated; there is simply distance between the "variable" and its initializer.

That has nothing to do with repetition.

It seems to me that a small, non-breaking syntax change could allow this:

Structured binding should not be used as a quick-and-dirty way to create multiple variables of different types in a place where that wasn't possible previously. And we certainly should not add syntax to encourage this.

Remember: they could have made `auto` do this exact thing when we standardized it in 2011. But the committee expressly decided against it. All of the reasons for not allowing it then are still just as valid now.


    for( auto [i = size_t(0), first = begin(v), last = end(v)]
       ; first != last
       ; ++i, ++first)
    {
        // ...
    }

which would:

a) be 100% DRY,
b) involve less typing

59 characters vs. 79. That's not "much less", especially compared to the 45 characters in the range-based version.
 
c) match the syntax for lambda capture (I could expand further on my thoughts on that, but perhaps another day)
d) IMHO be easier to teach

This isn't something people should be doing anyway. So why would we want it to be easy to teach?

Richard Hodges

unread,
Dec 13, 2017, 10:58:11 AM12/13/17
to std-pr...@isocpp.org
answer inline

On 13 December 2017 at 16:26, Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, December 13, 2017 at 5:15:56 AM UTC-5, Richard Hodges wrote:
A simple example:

Here's a fairly common idiom, expressed in terms of a structured binding:

I'm sure some people do this, but an indexed range view would handle this much more easily and compactly:

I had anticipated answers like this. Of course, there are other ways to implement this specific idiom. I have used it as a simple example of multiple initialisations of disparate types firmly within the scope of a for-loop
 

for(auto &[i, val] : std::counted_view(rng))
{...}

#include <iostream>
#include <vector>
#include <tuple>

int main()
{
    std::vector<int> v = { 6,5,4,3,2,1 };

    for( auto [i, first, last] = std::make_tuple(std::size_t(0), begin(v), end(v))
       ; first != last
       ; ++i, ++first)
    {
        std::cout << "index: " << i << "\t" << *first << "\n";
    }
}

It seems sensible to me to group the loop's variables in a structured binding since 

  • it's succinct and,
  • they remain firmly local to the loop's scope
  • It's the only way to declare multiple dissimilar variables within the for statement.

However, this code is not DRY - the naming of the bound variables must correspond to the creation of the tuple. Maintenance confusion awaits.

Perhaps you're mistaking DRY for some other principle. DRY is Don't Repeat Yourself. This code involves no repetition of typenames, variable names, objects, literals, or anything else. Nothing is being repeated; there is simply distance between the "variable" and its initializer.

That has nothing to do with repetition.

Fair enough, we'll settle on "distance". I think you'd agree that we'd prefer not to have this distance.
 

It seems to me that a small, non-breaking syntax change could allow this:

Structured binding should not be used as a quick-and-dirty way to create multiple variables of different types in a place where that wasn't possible previously. And we certainly should not add syntax to encourage this.

This is sounding like unsubstansiated opinion. And I respectfully disagree - it seems useful to me to be able to create a tuple with named arguments in a local slope on the fly.
 

Remember: they could have made `auto` do this exact thing when we standardized it in 2011. But the committee expressly decided against it. All of the reasons for not allowing it then are still just as valid now.

I am not privy to the reasons. Are you able to elaborate please? I don't think structured bindings were on the table back in 2011.
 


    for( auto [i = size_t(0), first = begin(v), last = end(v)]
       ; first != last
       ; ++i, ++first)
    {
        // ...
    }

which would:

a) be 100% DRY,
b) involve less typing

59 characters vs. 79. That's not "much less", especially compared to the 45 characters in the range-based version.

No, it's not "much less". It's "less", as I originally wrote. However, perhaps we digress. The primary benefit (marked "a" and formatted in bold) is that it is DRY. Which I think we would all agree is a good thing (even if we disagree on the method to achieve it).
 
c) match the syntax for lambda capture (I could expand further on my thoughts on that, but perhaps another day)
d) IMHO be easier to teach

This isn't something people should be doing anyway. So why would we want it to be easy to teach?

Please kindly provide me with the reasoning behind this (very strong) assertion. 


--
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.

Jake Arkinstall

unread,
Dec 13, 2017, 10:58:28 AM12/13/17
to std-pr...@isocpp.org


On 13 Dec 2017 15:26, "Nicol Bolas" <jmck...@gmail.com> wrote:
Perhaps you're mistaking DRY for some other principle. DRY is Don't Repeat Yourself. This code involves no repetition of typenames, variable names, objects, literals, or anything else. Nothing is being repeated; there is simply distance between the "variable" and its initializer.

That has nothing to do with repetition.

...
59 characters vs. 79. That's not "much less"

Your comments here sound like you are only considering the case of this occurring only once. It seems to me that the snippet provided is an example of something one may do multiple times in a program, and with the approach in the snippet it would violate DRY. Your provided approach is certainly nice, but limited to one use case.

I would like auto to allow multiple types for a different use case - iterating through N vectors whose elements are related by a common index when it isn't feasible to store them in as pairs in the same vector (e.g. a set of core data and some set of customisable metrics on that data that are only used in one part of the program). Sure, there may be some lovely templated ways around this (in my case I just take the iterator definitions outside the loop and make it a while. Ugly but functional. If there is some nice multi-iterator functionality in the STL, I sure as hell couldn't find it), but one nice way would be to allow their definitions be placed in the first for statement even though their types are different. 

This seems like a very attractive extension of current auto functionality. We can even rename it to "multi_auto" or something if it clashes with some obscure implicit casts (I'm not actually sure if that's a thing though). From my point of view, forcing types to be identical in the first statement of a for loop is a side-effect of the nature of single statements, not some useful piece of functionality that does us any favours.

Nicol Bolas

unread,
Dec 13, 2017, 12:05:58 PM12/13/17
to ISO C++ Standard - Future Proposals
And you can do that already. What we shouldn't do is encourage it or hide the fact that this is what you're doing.

Here's the thing. The only time you absolutely need this feature is when you're declaring variables in a place where you only get one declaration. Because if you can make multiple declarations, there's nothing stopping you from doing:

auto i = size_t(0);
auto first = begin(v);
auto last = end(v);

I find these lines to be far more readable than the structured binding version or your alternative structured binding syntax. So the only time people should use your alternate syntax is when they're only allowed a single declaration. And that means in the initializer section of some statement.

Do we really need to add special syntax for something that only gets used in such limited scenarios? Even worse, if we add such syntax, aren't we encouraging people to declare a bunch of variables in one line, which is generally considered bad form?

No, it's best to just leave things as they are. In those circumstances where it is necessary to achieve what you want, you can still do it. But the poor syntax and copying behavior will encourage you to avoid it whenever possible.

Remember: they could have made `auto` do this exact thing when we standardized it in 2011. But the committee expressly decided against it. All of the reasons for not allowing it then are still just as valid now.

I am not privy to the reasons. Are you able to elaborate please? I don't think structured bindings were on the table back in 2011.

I didn't say structured bindings. I said `auto`.

Remember: what you want is not structured bindings. Structured binding is about unpacking objects that store data into multiple variable-like constructs. You don't have such a object, and you don't really want one. The only reason you created a tuple of values was because structured binding requires it.

What you really want is the ability to declare multiple variables of different types in a single declaration statement. When `auto` was being standardized, there was discussion about allowing precisely that:

auto x = 5.0f, y = 10, z = std::string("foo");

These would deduce 3 different types for the 3 variables. The committee specifically decided against doing this.

Through structured bindings, you can achieve a similar effect as that. But if we wanted that effect, we'd have allowed it to begin with.


    for( auto [i = size_t(0), first = begin(v), last = end(v)]
       ; first != last
       ; ++i, ++first)
    {
        // ...
    }

which would:

a) be 100% DRY,
b) involve less typing

59 characters vs. 79. That's not "much less", especially compared to the 45 characters in the range-based version.

No, it's not "much less". It's "less", as I originally wrote. However, perhaps we digress. The primary benefit (marked "a" and formatted in bold) is that it is DRY.

But it isn't DRY; as we previously agreed, there's no repetition being eliminated. There is only distance.
 
Which I think we would all agree is a good thing (even if we disagree on the method to achieve it).

No, not really. I don't want to see code like that, and I don't want to encourage people to have such long variable declaration sequences all in one statement like that.

If you're having to break a `for` loop or `if` statement or whatever into multiple lines just to make them legible, it suggests that there's a problem in your code. The longer such statements get, the harder it is to follow what's going on.

Indeed, reducing such verbosity is partially why we created range-based `for` in the first place. The idiom you're talking about is not an anti-pattern per-se, but it ought to be considered a code smell. When you use it, it should be because there is no better way to do what you're doing.

Richard Hodges

unread,
Dec 13, 2017, 5:22:11 PM12/13/17
to std-pr...@isocpp.org
inline....

In current c++, in order to declare these variables one per line while enclosing them strictly within the scope of the algorithm, once would need to enclose the entire algorithm in a pair of logically un-neccessary curly braces. Of course one way to do that is to enclose the entire construct in a lambda or mini-function. However, this is not always desirable. Which of course is why we have range-based for in addition to std::for_each().
 
Do we really need to add special syntax for something that only gets used in such limited scenarios? Even worse, if we add such syntax, aren't we encouraging people to declare a bunch of variables in one line, which is generally considered bad form?

It seems to me that tuples have become a special kind of problem, in that there is core language support for half of the construct, but library-only support for the rest. Similarly initializer_list and typeinfo. I am of the view that this is an aberration. Either support it in the core language or don't. The current half and half approach is an error. why shouldn't I be able to say:

auto t = auto [x = int(1), y = std::string("foo"), 6];  // get<0>(t) == 1, get<1>(t) == "foo", get<2>(t) == 6
 
such an expression is expressive, succinct and useful. I can manipulate the tuple either as a complete object or as individual objects. Great!


No, it's best to just leave things as they are. In those circumstances where it is necessary to achieve what you want, you can still do it. But the poor syntax and copying behavior will encourage you to avoid it whenever possible.

An "it's best to leave things as they are" attitude would leave us with c+98 and prevent c++ ever becoming a mature fully-featured expressive language. I'm afraid to say that I cannot take this statement seriously.
 

Remember: they could have made `auto` do this exact thing when we standardized it in 2011. But the committee expressly decided against it. All of the reasons for not allowing it then are still just as valid now.

I am not privy to the reasons. Are you able to elaborate please? I don't think structured bindings were on the table back in 2011.

I didn't say structured bindings. I said `auto`.

Mea culpa.
 

Remember: what you want is not structured bindings. Structured binding is about unpacking objects that store data into multiple variable-like constructs. You don't have such a object, and you don't really want one. The only reason you created a tuple of values was because structured binding requires it.

What you really want is the ability to declare multiple variables of different types in a single declaration statement. When `auto` was being standardized, there was discussion about allowing precisely that:

auto x = 5.0f, y = 10, z = std::string("foo");

These would deduce 3 different types for the 3 variables. The committee specifically decided against doing this.

Deciding against this seems to me to have been, in hindsight, the wrong decision. Perhaps this error can be corrected in a future standard? I have yet to see a reasonable rationalisation (in the light of since-gathered experience) of this decision.
 

Through structured bindings, you can achieve a similar effect as that. But if we wanted that effect, we'd have allowed it to begin with.


    for( auto [i = size_t(0), first = begin(v), last = end(v)]
       ; first != last
       ; ++i, ++first)
    {
        // ...
    }

which would:

a) be 100% DRY,
b) involve less typing

59 characters vs. 79. That's not "much less", especially compared to the 45 characters in the range-based version.

No, it's not "much less". It's "less", as I originally wrote. However, perhaps we digress. The primary benefit (marked "a" and formatted in bold) is that it is DRY.

But it isn't DRY; as we previously agreed, there's no repetition being eliminated. There is only distance.

We will have to disagree. I say the repetition is in the correlation of numbers and positions of terms in the structured binding and the tuple creation.
 
 
Which I think we would all agree is a good thing (even if we disagree on the method to achieve it).

No, not really. I don't want to see code like that, and I don't want to encourage people to have such long variable declaration sequences all in one statement like that.

You are saying that you don't agree that DRY is a good thing on grounds of aesthetics? This position seems indefensible to me.
 

If you're having to break a `for` loop or `if` statement or whatever into multiple lines just to make them legible, it suggests that there's a problem in your code. The longer such statements get, the harder it is to follow what's going on.

I have to say that since variable names are often longer than 10 characters, breaking a for loop into 3 or so lines is almost always inevitable.
 

Indeed, reducing such verbosity is partially why we created range-based `for` in the first place. The idiom you're talking about is not an anti-pattern per-se, but it ought to be considered a code smell. When you use it, it should be because there is no better way to do what you're doing.


Range-based for solves exactly one class of common problem, not all of them. It's useful, and thanks for creating it. But it's not useful for everything. It's not that I am ungrateful. I simply happen to think we can do even better.


--
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.

Nicol Bolas

unread,
Dec 13, 2017, 6:48:47 PM12/13/17
to ISO C++ Standard - Future Proposals
On Wednesday, December 13, 2017 at 5:22:11 PM UTC-5, Richard Hodges wrote:
inline....

On 13 December 2017 at 18:05, Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, December 13, 2017 at 10:58:11 AM UTC-5, Richard Hodges wrote:
answer inline


It seems to me that a small, non-breaking syntax change could allow this:

Structured binding should not be used as a quick-and-dirty way to create multiple variables of different types in a place where that wasn't possible previously. And we certainly should not add syntax to encourage this.

This is sounding like unsubstansiated opinion. And I respectfully disagree - it seems useful to me to be able to create a tuple with named arguments in a local slope on the fly.

And you can do that already. What we shouldn't do is encourage it or hide the fact that this is what you're doing.

Here's the thing. The only time you absolutely need this feature is when you're declaring variables in a place where you only get one declaration. Because if you can make multiple declarations, there's nothing stopping you from doing:

auto i = size_t(0);
auto first = begin(v);
auto last = end(v);

I find these lines to be far more readable than the structured binding version or your alternative structured binding syntax. So the only time people should use your alternate syntax is when they're only allowed a single declaration. And that means in the initializer section of some statement.


In current c++, in order to declare these variables one per line while enclosing them strictly within the scope of the algorithm, once would need to enclose the entire algorithm in a pair of logically un-neccessary curly braces.

... huh? If you have some variables that are "strictly within the scope of the algorithm", then that algorithm must already have scope. So where would these "logically un-neccessary curly braces" need to come from? Unless "scope of the algorithm" should somehow not involve a C++ scope.

And if the algorithm's scope isn't bounded by C++ scope, what does it matter if its one variable or 20; none of these variables are strictly within the scope of the algorithm. They are within the C++ scope, which we've just stated isn't the algorithm's scope.

Do we really need to add special syntax for something that only gets used in such limited scenarios? Even worse, if we add such syntax, aren't we encouraging people to declare a bunch of variables in one line, which is generally considered bad form?

It seems to me that tuples have become a special kind of problem, in that there is core language support for half of the construct, but library-only support for the rest.

There is no core language support for any part of `tuple`. What we have is core language support for decomposition, and `std::tuple` is a decomposable type. But any user-created type can be decomposable with the right definition or machinery.

Don't think of structured binding as a thing that works on tuples, with tuples, or is about tuples.

Similarly initializer_list and typeinfo. I am of the view that this is an aberration.

I don't even know what you're talking about with these. `type_info` is a C++ object returned by `typeid`, which allows basic queries about types to work without creating some new C++ construct that you have to define a bunch of rules for. `initializer_list` is much the same.
 
Either support it in the core language or don't. The current half and half approach is an error. why shouldn't I be able to say:

auto t = auto [x = int(1), y = std::string("foo"), 6];  // get<0>(t) == 1, get<1>(t) == "foo", get<2>(t) == 6
 
such an expression is expressive, succinct and useful. I can manipulate the tuple either as a complete object or as individual objects. Great!

I fail to see how that is an improvement over using a tuple. And quite frankly, we already have ways of declaring a type:

struct {int x = 1; std::string y{"foo"}; int z = 6;} t;

Oh sure, you can't leave a member unnamed, but why would you want to? If that member is important enough to be stored and accessed, it's important enough to have a name.

`std::tuple` primarily exists for metaprogramming reasons (building structs, etc). If you're using it in static code like the declaration above, you're almost always using the wrong tool.

No, it's best to just leave things as they are. In those circumstances where it is necessary to achieve what you want, you can still do it. But the poor syntax and copying behavior will encourage you to avoid it whenever possible.

An "it's best to leave things as they are" attitude would leave us with c+98 and prevent c++ ever becoming a mature fully-featured expressive language. I'm afraid to say that I cannot take this statement seriously.

I didn't say we should leave all of C++ as it is. I'm saying that we should leave this as it is.

My problems with this really boil down to (in priority order):

1) It abuses structured binding, forcing it to do something that structured binding isn't meant to do simply because it is convenient syntax.

2) The problems this solves are not encountered frequently enough to override #1.

3) In a lot of the practical cases where this might be useful, there are better solutions that don't need it (like my range adaptor example).

4) It encourages writing ugly code. The more variables you shove into a `for` initializer, the more testing and incrementing expressions you need and the harder it is to follow the logic. This is a big part of why the range adaptor is preferred where possible; it hides the ugly details.

Remember: they could have made `auto` do this exact thing when we standardized it in 2011. But the committee expressly decided against it. All of the reasons for not allowing it then are still just as valid now.

I am not privy to the reasons. Are you able to elaborate please? I don't think structured bindings were on the table back in 2011.

I didn't say structured bindings. I said `auto`.

Mea culpa.
 

Remember: what you want is not structured bindings. Structured binding is about unpacking objects that store data into multiple variable-like constructs. You don't have such a object, and you don't really want one. The only reason you created a tuple of values was because structured binding requires it.

What you really want is the ability to declare multiple variables of different types in a single declaration statement. When `auto` was being standardized, there was discussion about allowing precisely that:

auto x = 5.0f, y = 10, z = std::string("foo");

These would deduce 3 different types for the 3 variables. The committee specifically decided against doing this.

Deciding against this seems to me to have been, in hindsight, the wrong decision. Perhaps this error can be corrected in a future standard?

Well, changing it is possible. At present, [dcl.spec.auto]/7 says:

> The type of each declared variable is determined by placeholder type deduction (10.1.7.4.1), and if the type that replaces the placeholder type is not the same in each deduction, the program is ill-formed.

This means that every declaration which would deduce different types is at present illegal. So removing this restriction would not break any currently legal code.

And this change would be a lot more palatable than (ab)using structured binding for this purpose. It would also have semantics that are more immediately obvious than those for (ab)using structured binding in this way.

Though personally, I still think it's a bad idea.

I have yet to see a reasonable rationalisation (in the light of since-gathered experience) of this decision.

It's you who have to come up with a rationalization for the change, not the other way around. If "the light of since-gathered experience" really is on your side, you ought to be able to look at all of the old arguments against it and answer them one-by-one based on that experience.

Be sure to bring up the Range TS Iterator/Sentinel paradigm. That's always a slam-dunk when it comes to deducing the same type ;)

The `auto` proposals for C++11 are all still available from the committee's website. Feel free to download them and start working through the counter-arguments.

Richard Hodges

unread,
Dec 14, 2017, 2:27:39 AM12/14/17
to std-pr...@isocpp.org
I won't dwell too much, just a couple of comments:



Declaring an object of on-the-fly struct would be a solution if a struct's members could be declared auto. Unhappily they cannot, so this solution would only work where types are well known. Of course the argument for introducing auto hinged on convenience of type deduction, particularly in template expansions. This is a convenience I think we all enjoy. I would like to see this kind of convenience extended.

As a side note, there is exactly one core language feature in which the types of members is automatically deduced - the lambda with capture. The automatic deduction is useful in this case, it might well be in others.
 
Oh sure, you can't leave a member unnamed, but why would you want to? If that member is important enough to be stored and accessed, it's important enough to have a name.

`std::tuple` primarily exists for metaprogramming reasons (building structs, etc). If you're using it in static code like the declaration above, you're almost always using the wrong tool.

No, it's best to just leave things as they are. In those circumstances where it is necessary to achieve what you want, you can still do it. But the poor syntax and copying behavior will encourage you to avoid it whenever possible.

An "it's best to leave things as they are" attitude would leave us with c+98 and prevent c++ ever becoming a mature fully-featured expressive language. I'm afraid to say that I cannot take this statement seriously.

I didn't say we should leave all of C++ as it is. I'm saying that we should leave this as it is.

My problems with this really boil down to (in priority order):

1) It abuses structured binding, forcing it to do something that structured binding isn't meant to do simply because it is convenient syntax.

Tuples and structures are _logically_ two flavours of the same thing. We happen to talk about them as if they are different for historic reasons, not logical ones. For me, the ability to spontaneously create an object of an arbitrary compound type, and inject the names of its members into local scope seems useful. It is this feature that gives python (for example) its wonderfully succinct list-comprehension syntax.
 

2) The problems this solves are not encountered frequently enough to override #1.

Assertion. New tools create new opportunities.
 

3) In a lot of the practical cases where this might be useful, there are better solutions that don't need it (like my range adaptor example).

Building a new iterator type for every logical scenario is inconvenient and difficult to teach. It hides logic details. This is why range-based-for was invented. "keep simple things simple".
 

4) It encourages writing ugly code. The more variables you shove into a `for` initializer, the more testing and incrementing expressions you need and the harder it is to follow the logic. This is a big part of why the range adaptor is preferred where possible; it hides the ugly details.

Remember: they could have made `auto` do this exact thing when we standardized it in 2011. But the committee expressly decided against it. All of the reasons for not allowing it then are still just as valid now.

I am not privy to the reasons. Are you able to elaborate please? I don't think structured bindings were on the table back in 2011.

I didn't say structured bindings. I said `auto`.

Mea culpa.
 

Remember: what you want is not structured bindings. Structured binding is about unpacking objects that store data into multiple variable-like constructs. You don't have such a object, and you don't really want one. The only reason you created a tuple of values was because structured binding requires it.

What you really want is the ability to declare multiple variables of different types in a single declaration statement. When `auto` was being standardized, there was discussion about allowing precisely that:

auto x = 5.0f, y = 10, z = std::string("foo");

These would deduce 3 different types for the 3 variables. The committee specifically decided against doing this.

Deciding against this seems to me to have been, in hindsight, the wrong decision. Perhaps this error can be corrected in a future standard?

Well, changing it is possible. At present, [dcl.spec.auto]/7 says:

> The type of each declared variable is determined by placeholder type deduction (10.1.7.4.1), and if the type that replaces the placeholder type is not the same in each deduction, the program is ill-formed.

This means that every declaration which would deduce different types is at present illegal. So removing this restriction would not break any currently legal code.

And this change would be a lot more palatable than (ab)using structured binding for this purpose. It would also have semantics that are more immediately obvious than those for (ab)using structured binding in this way.

Though personally, I still think it's a bad idea.

Do you mind explaining why? Because to me it seems like a splendid idea. To me, auto x = foo(), y = bar(); seems succinct and neat. Someone who didn't know the language would read this and naturally conclude that x and y may be of different types.

I have yet to see a reasonable rationalisation (in the light of since-gathered experience) of this decision.

It's you who have to come up with a rationalization for the change, not the other way around. If "the light of since-gathered experience" really is on your side, you ought to be able to look at all of the old arguments against it and answer them one-by-one based on that experience.

For one, it's more naturally logical to imagine that an auto variable will have the type of its initialiser. 

Be sure to bring up the Range TS Iterator/Sentinel paradigm. That's always a slam-dunk when it comes to deducing the same type ;)


I'm not sure what you mean here. In any range-based algorithm you will certainly want the ability for the iterator to be of a different type to the sentinel. multi-typed auto would help this.
 

The `auto` proposals for C++11 are all still available from the committee's website. Feel free to download them and start working through the counter-arguments.

--
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.

Richard Hodges

unread,
Dec 14, 2017, 7:37:22 AM12/14/17
to std-pr...@isocpp.org
Having pondered the responses, it turns out that I can express my algorithm in a tightly scoped, DRY and legal manner in current c++ like this:

      
// imagine there is an f(auto&& elem, auto&& index)

using std::begin, std::end;
[&f, i = std::size_t(0), first = begin(v), last = end(v)]() mutable
{
while(first != last)
f(*first++, i++);
}();

This is of course a horrid abuse of lambdas and is certainly the kind of code that should be discouraged - except that it's actually doing what I want - which is allowing me to create more than one loop-scoped variable in a template-friendly manner.
Of course, I could write a function for this, but in reality what I am doing here is a 2-liner. It doesn't warrant figuring out a reasonable function name, documenting the function and deciding which header file it'll live in (forever).

Horrid as it is, what it shows is that we are already familiar with the syntax of enumerating variable creation and allowing the compiler to generate a class (tuple) for us.

I really do think that there is a case for allowing:

for(auto [i = std::size_t(0)] ; auto&& elem : v)
f(elem, i++);

or:

for(auto [i = std::size_t(0), first = begin(v), last = end(v)] ; first != last ; ++first, ++i)
f(*first, i);

or:

for(auto i = std::size_t(0), first = begin(v), last = end(v) ; first != last ; ++first, ++i)
f(*first, i);

since we already have this ability in the nasty lambda above, why not provide the ability in a way that's easy to reason about?



David Brown

unread,
Dec 14, 2017, 9:16:40 AM12/14/17
to std-pr...@isocpp.org
You can currently use the syntax of this last version in some cases -
but it means something slightly different. Multiple variables declared
in one "auto" declaration need the same type.

I would go for the syntax:

for (auto i = std::size_t(0); auto first = begin(v); auto last = end(v);
first != last; ++first, ++i) { ... }

Simply allow as many initialiser declarations as you want - using auto
or specific types. There should be no problem in parsing - the last two
clauses are still in the same place. And the same idea can apply to
while(), if(), etc.

An alternative would be a more generic language change - allow a
declaration of the form "t1 x1, t2 x2;" to have the same meaning as "t1
x1; t2 x2;".

If there is to be a proposal to allow initialisers with more than one
type here, I don't think it should be restricted to "auto" - I think we
want it to include specific types, and concepts. Thus structured
bindings is not the answer.




adrian....@gmail.com

unread,
Dec 14, 2017, 9:27:59 AM12/14/17
to ISO C++ Standard - Future Proposals
> I'm sure some people do this, but an indexed range view would handle this much more easily and compactly:
>
> for(auto &[i, val] : std::counted_view(rng))
> {...}

What standard is that from? Couldn't find it when searching for std counted_view.

Message has been deleted

Richard Hodges

unread,
Dec 14, 2017, 10:00:45 AM12/14/17
to std-pr...@isocpp.org
> I'm sure some people do this, but an indexed range view would handle this much more easily and compactly:
>
> for(auto &[i, val] : std::counted_view(rng))
> {...}

There is currently no such thing as a std::counted_view but I take your point. The problems with this approach are:

  1. You have to think of a meaningful name for every trivial range adapter you ever want to write. This is a bigger problem in a large organisation or distributed team that it might at first seem.
  2. Range adapters involve abstracting out one's algorithm into a verbose class. Precisely the opposite of the intent of range-based for (keep simple things simple, keep logic in the loop body)
  3. You have to document and publish what is essentially a line or 2 of trivial self-documenting code.
  4. You have to wait for at least 3 years of squabbling in the isocpp committee before it'll be in the std namespace*
* ok, fair enough, you could submit it to boost and get it out in 3 months if you're happy with boost::counted_view.


On 14 December 2017 at 15:34, <adrian....@gmail.com> wrote:
> I'm sure some people do this, but an indexed range view would handle this much more easily and compactly:

> for(auto &[i, val] : std::counted_view(rng))
> {...}

I've also never seen `auto &[i, val]` as a variable declaration consruct before. Is that c++17?

Yes, that's a structured binding. It unpacks a tuple or struct into (in this case) two references in the current scope. 

--
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.

Nicol Bolas

unread,
Dec 14, 2017, 12:19:47 PM12/14/17
to ISO C++ Standard - Future Proposals
On Thursday, December 14, 2017 at 2:27:39 AM UTC-5, Richard Hodges wrote:
I won't dwell too much, just a couple of comments:
On 14 December 2017 at 00:48, Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, December 13, 2017 at 5:22:11 PM UTC-5, Richard Hodges wrote:
inline....

On 13 December 2017 at 18:05, Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, December 13, 2017 at 10:58:11 AM UTC-5, Richard Hodges wrote: 
Either support it in the core language or don't. The current half and half approach is an error. why shouldn't I be able to say:

auto t = auto [x = int(1), y = std::string("foo"), 6];  // get<0>(t) == 1, get<1>(t) == "foo", get<2>(t) == 6
 
such an expression is expressive, succinct and useful. I can manipulate the tuple either as a complete object or as individual objects. Great!

I fail to see how that is an improvement over using a tuple. And quite frankly, we already have ways of declaring a type:

struct {int x = 1; std::string y{"foo"}; int z = 6;} t;


Declaring an object of on-the-fly struct would be a solution if a struct's members could be declared auto.

Seriously, what is it with people and `auto`? Why are you so allergic to typenames? That structure as it stands is far more readable than the "auto" version would be.

Thinking like this makes me rue the day the committee ever standardized `auto` variable deduction. People keep trying to take a useful tool for dealing with obvious code and long typenames, and turning it into the thing that must be used everywhere.

Let's not standardize the Almost-Always-Auto ideology.

Unhappily they cannot, so this solution would only work where types are well known. Of course the argument for introducing auto hinged on convenience of type deduction, particularly in template expansions. This is a convenience I think we all enjoy. I would like to see this kind of convenience extended.

"Convenience" of this sort often creates ambiguity and confusion. Having to use a typename is not some kind of onerous burden here.

As a side note, there is exactly one core language feature in which the types of members is automatically deduced - the lambda with capture. The automatic deduction is useful in this case, it might well be in others.

That's because lambdas have to be short to be useful, particularly the capture list. The main point of a lambda is not what it captures but the actual function it encapsulates. The capture part is an unfortunate implementation detail that we try to minimize where possible.

That is, the point of a lambda is the function part, not the functor part. The functor part is an implementation detail required by the way C++ works.

Making struct definitions shorter makes them harder to reason about and digest, and gives precious little back in return.

No, it's best to just leave things as they are. In those circumstances where it is necessary to achieve what you want, you can still do it. But the poor syntax and copying behavior will encourage you to avoid it whenever possible.

An "it's best to leave things as they are" attitude would leave us with c+98 and prevent c++ ever becoming a mature fully-featured expressive language. I'm afraid to say that I cannot take this statement seriously.

I didn't say we should leave all of C++ as it is. I'm saying that we should leave this as it is.

My problems with this really boil down to (in priority order):

1) It abuses structured binding, forcing it to do something that structured binding isn't meant to do simply because it is convenient syntax.

Tuples and structures are _logically_ two flavours of the same thing. We happen to talk about them as if they are different for historic reasons, not logical ones.

... I don't know what that has to do with what I said. Your statement only makes sense if you think of structured binding as a feature invented for tuples. It's not. It works just fine with decomposeable structs, and you can write machinery to make them decomposeable when they're not automatically decomposeable.

For me, the ability to spontaneously create an object of an arbitrary compound type, and inject the names of its members into local scope seems useful. It is this feature that gives python (for example) its wonderfully succinct list-comprehension syntax.

That doesn't justify abusing structured binding to do that. If you really believe that the ability to create a type and make local variables of its members is useful (and I've yet to see how), then you should create a feature that is specifically about that.

Structured binding is about unpacking an existing object, not about creating types. Don't abuse a syntax for an unrelated feature just because it's convenient.

2) The problems this solves are not encountered frequently enough to override #1.

Assertion. New tools create new opportunities.

Also an assertion. What "new opportunities" does this create?

3) In a lot of the practical cases where this might be useful, there are better solutions that don't need it (like my range adaptor example).

Building a new iterator type for every logical scenario is inconvenient and difficult to teach. It hides logic details.

Hiding details for complex iteration scenarios is exactly what you ought to do. Show me the code using a non-range adaptor that iterates over two ranges, accessing the value from each range. Feel free to use whatever `auto` syntax you want to initialize multiple variables. But you're not allowed to use a standard library algorithm.

Now compare that `for` loop to the range adapter version:

for(auto &[first, second] : zip_range(rng_first, rng_second))
{}

There's no contest here. Hiding the "logical details" makes this code far more convenient and easier to teach. The same goes for so many iteration scenarios. Iterating over a group of ranges in sequence. Iterating over each range in turn. And so on.

The more iteration variables you see in a loop declaration, the more iterator expressions it has, the harder it is to follow the logic. Those ranges are idiomatic and easily comprehensible.

There will always be corner cases, one-off iteration scenarios where existing tools don't quite match what you need. But those are corner cases; we don't build language features just for corner cases.

If a "logical scenario" is frequently encountered, and it fits into the range paradigm, then making a range type for it is precisely what one ought to do.

This is why range-based-for was invented. "keep simple things simple".'

And the point of range adaptors is to do precisely that. Iterating over a pair of range or a sequence of ranges ought to be a simple thing. And with range adaptors, they are.

With your idea, it makes them difficult to work with.

Remember: what you want is not structured bindings. Structured binding is about unpacking objects that store data into multiple variable-like constructs. You don't have such a object, and you don't really want one. The only reason you created a tuple of values was because structured binding requires it.

What you really want is the ability to declare multiple variables of different types in a single declaration statement. When `auto` was being standardized, there was discussion about allowing precisely that:

auto x = 5.0f, y = 10, z = std::string("foo");

These would deduce 3 different types for the 3 variables. The committee specifically decided against doing this.

Deciding against this seems to me to have been, in hindsight, the wrong decision. Perhaps this error can be corrected in a future standard?

Well, changing it is possible. At present, [dcl.spec.auto]/7 says:

> The type of each declared variable is determined by placeholder type deduction (10.1.7.4.1), and if the type that replaces the placeholder type is not the same in each deduction, the program is ill-formed.

This means that every declaration which would deduce different types is at present illegal. So removing this restriction would not break any currently legal code.

And this change would be a lot more palatable than (ab)using structured binding for this purpose. It would also have semantics that are more immediately obvious than those for (ab)using structured binding in this way.

Though personally, I still think it's a bad idea.

Do you mind explaining why? Because to me it seems like a splendid idea. To me, auto x = foo(), y = bar(); seems succinct and neat. Someone who didn't know the language would read this and naturally conclude that x and y may be of different types.

I don't like it precisely because it encourages you to type `auto x = foo(), y = bar();`. Those are two independent operations; two separate components of whatever algorithm you're writing. Unless there is some obvious connection between `foo` and `bar`, they have no business being on the same line.

It's bad coding style, and it ought to fail code review.

I have yet to see a reasonable rationalisation (in the light of since-gathered experience) of this decision.

It's you who have to come up with a rationalization for the change, not the other way around. If "the light of since-gathered experience" really is on your side, you ought to be able to look at all of the old arguments against it and answer them one-by-one based on that experience.

For one, it's more naturally logical to imagine that an auto variable will have the type of its initialiser.

That's not an answer to those arguments. Indeed, you didn't even state what those arguments are.

Be sure to bring up the Range TS Iterator/Sentinel paradigm. That's always a slam-dunk when it comes to deducing the same type ;)

I'm not sure what you mean here. In any range-based algorithm you will certainly want the ability for the iterator to be of a different type to the sentinel. multi-typed auto would help this.

... right. Which is why I said you should bring it up. I wasn't being sarcastic.

Consider what I just said about having two "independent operations" on the same line. If you're extracting begin/end from a range, then those aren't "two independent operations". They're a single logical thought:

auto beg = rng.begin(), end = rng.end();

When you have something that is conceptually atomic that, by reason of implementation, requires two distinct actions, that is when it is appropriate to stick them in one line.

Of course, you'll naturally try to say that getting iterators for two separate ranges is also a single logical thought to your algorithm. But I contest this because they're not atomic actions. Getting the beginning of a range is useless without getting its end (in most cases). That's what makes the pair of actions "atomic"; you can't do anything with them until both are complete.

However, getting one iterator range can be useful without getting some other iterator range. It may not be useful to your algorithm, but it is still useful in the general sense.

That's my personal dividing line.

On Thursday, December 14, 2017 at 7:37:22 AM UTC-5, Richard Hodges wrote:
Having pondered the responses, it turns out that I can express my algorithm in a tightly scoped, DRY and legal manner in current c++ like this:

I'm not really sure what that proves. You can accomplish the same thing by using curly braces:

{
  std
::size_t ix = 0;

 
auto first = begin(v);
 
auto last = end(v);

 
for(; first != last; ++first, ++ix)
   
//Actual loop body;
}

You can even remove the `begin` line by adding it to the `for` statement. So what's the downside of this? Do you really need to write these multi-variable things that often that we need a language change? You've not answered that question.

I really do think that there is a case for allowing:

    for(auto [i = std::size_t(0)] ; auto&& elem : v)
            f(elem, i++);

or:

    for(auto [i = std::size_t(0), first = begin(v), last = end(v)] ; first != last ; ++first, ++i)
            f(*first, i);

or:

    for(auto i = std::size_t(0), first = begin(v), last = end(v) ; first != last ; ++first, ++i)
            f(*first, i);

since we already have this ability in the nasty lambda above, why not provide the ability in a way that's easy to reason about?

You can already do the first one; just get rid of the structured binding bit. And the second and third ones have nothing to do with one another. They have completely different semantics.

Richard Hodges

unread,
Dec 14, 2017, 1:20:12 PM12/14/17
to std-pr...@isocpp.org
On 14 December 2017 at 18:19, Nicol Bolas <jmck...@gmail.com> wrote:
On Thursday, December 14, 2017 at 2:27:39 AM UTC-5, Richard Hodges wrote:
I won't dwell too much, just a couple of comments:
On 14 December 2017 at 00:48, Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, December 13, 2017 at 5:22:11 PM UTC-5, Richard Hodges wrote:
inline....

On 13 December 2017 at 18:05, Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, December 13, 2017 at 10:58:11 AM UTC-5, Richard Hodges wrote: 
Either support it in the core language or don't. The current half and half approach is an error. why shouldn't I be able to say:

auto t = auto [x = int(1), y = std::string("foo"), 6];  // get<0>(t) == 1, get<1>(t) == "foo", get<2>(t) == 6
 
such an expression is expressive, succinct and useful. I can manipulate the tuple either as a complete object or as individual objects. Great!

I fail to see how that is an improvement over using a tuple. And quite frankly, we already have ways of declaring a type:

struct {int x = 1; std::string y{"foo"}; int z = 6;} t;


Declaring an object of on-the-fly struct would be a solution if a struct's members could be declared auto.

Seriously, what is it with people and `auto`? Why are you so allergic to typenames? That structure as it stands is far more readable than the "auto" version would be.

spelling out typenames makes template programming hard and verbose. This is why auto was introduced, as you well know.
 

Thinking like this makes me rue the day the committee ever standardized `auto` variable deduction. People keep trying to take a useful tool for dealing with obvious code and long typenames, and turning it into the thing that must be used everywhere.

People find their own uses for language features. auto is incredibly useful for a number of reasons. less verbosity, more succinct statement of abstract intent, easier refactoring, etc.
 

Let's not standardize the Almost-Always-Auto ideology.

Too late. Having to spell out typenames is primitive and vile, because intent gets lost amongst the irrelevant noise of long type names. A language ought to be both correct and enjoyable to use. auto helps very much in both regards. If you really are not using auto, it could be because you don't have to deliver correct solutions in a timely manner any more. auto has massively increased our productivity.
 

Unhappily they cannot, so this solution would only work where types are well known. Of course the argument for introducing auto hinged on convenience of type deduction, particularly in template expansions. This is a convenience I think we all enjoy. I would like to see this kind of convenience extended.

"Convenience" of this sort often creates ambiguity and confusion. Having to use a typename is not some kind of onerous burden here.

I disagree. "convenience" is almost always synonymous with "easier to correctly maintain and reason about". The more naturally we can express ideas in code, the easier time code-maintainers will have in understanding the original author's intent. Intent is a human concept, a computer merely blindly follows orders. The more intent-like code is expressed, the more likely it is to be correct. 
 

As a side note, there is exactly one core language feature in which the types of members is automatically deduced - the lambda with capture. The automatic deduction is useful in this case, it might well be in others.

That's because lambdas have to be short to be useful, particularly the capture list. The main point of a lambda is not what it captures but the actual function it encapsulates. The capture part is an unfortunate implementation detail that we try to minimize where possible.

That is, the point of a lambda is the function part, not the functor part. The functor part is an implementation detail required by the way C++ works.

A lambda is logically equivalent to a tuple/struct that happens to have a call operator. Lambdas are as much a code-writing service as templates are. std::tuple is a code-writing service too. Lambda notation is a low-noise solution for creation first class function objects. There's logically no reason at all that auto a = [ x=foo(), y = bar(), &rest... ] can't be a low-noise solution for creating on-the-fly composite objects with easy member visibility.  The intent is easily clear to any reader, and it replaces this abomination:

auto [ x, y /* what now? */ ] = std::tuple(foo(), bar(), std::ref(rest)...);
//                                                       ^ ugh - this is so un-necessary!


 

Making struct definitions shorter makes them harder to reason about and digest, and gives precious little back in return.

No, it's best to just leave things as they are. In those circumstances where it is necessary to achieve what you want, you can still do it. But the poor syntax and copying behavior will encourage you to avoid it whenever possible.

An "it's best to leave things as they are" attitude would leave us with c+98 and prevent c++ ever becoming a mature fully-featured expressive language. I'm afraid to say that I cannot take this statement seriously.

I didn't say we should leave all of C++ as it is. I'm saying that we should leave this as it is.

My problems with this really boil down to (in priority order):

1) It abuses structured binding, forcing it to do something that structured binding isn't meant to do simply because it is convenient syntax.

Tuples and structures are _logically_ two flavours of the same thing. We happen to talk about them as if they are different for historic reasons, not logical ones.

... I don't know what that has to do with what I said. Your statement only makes sense if you think of structured binding as a feature invented for tuples. It's not. It works just fine with decomposeable structs, and you can write machinery to make them decomposeable when they're not automatically decomposeable.

For me, the ability to spontaneously create an object of an arbitrary compound type, and inject the names of its members into local scope seems useful. It is this feature that gives python (for example) its wonderfully succinct list-comprehension syntax.

That doesn't justify abusing structured binding to do that. If you really believe that the ability to create a type and make local variables of its members is useful (and I've yet to see how), then you should create a feature that is specifically about that. 

Structured binding is about unpacking an existing object, not about creating types. Don't abuse a syntax for an unrelated feature just because it's convenient.

2) The problems this solves are not encountered frequently enough to override #1.

Assertion. New tools create new opportunities.

Also an assertion. What "new opportunities" does this create?

How do we know, until we start thinking in terms of opportunity, rather than in terms of squashing ideas from the user base?
Then we are at opposite ends of the spectrum here. I do not agree that "succinct expression of abstract intent" equals "bad coding style".


I have yet to see a reasonable rationalisation (in the light of since-gathered experience) of this decision.

It's you who have to come up with a rationalization for the change, not the other way around. If "the light of since-gathered experience" really is on your side, you ought to be able to look at all of the old arguments against it and answer them one-by-one based on that experience.

For one, it's more naturally logical to imagine that an auto variable will have the type of its initialiser.

That's not an answer to those arguments. Indeed, you didn't even state what those arguments are.

Be sure to bring up the Range TS Iterator/Sentinel paradigm. That's always a slam-dunk when it comes to deducing the same type ;)

I'm not sure what you mean here. In any range-based algorithm you will certainly want the ability for the iterator to be of a different type to the sentinel. multi-typed auto would help this.

... right. Which is why I said you should bring it up. I wasn't being sarcastic.

Consider what I just said about having two "independent operations" on the same line. If you're extracting begin/end from a range, then those aren't "two independent operations". They're a single logical thought:

auto beg = rng.begin(), end = rng.end();

When you have something that is conceptually atomic that, by reason of implementation, requires two distinct actions, that is when it is appropriate to stick them in one line.

I think we agree here. Acquiring an index counter and the limits of a range seem to me to be part of the same logical atomic operation. 

Of course, you'll naturally try to say that getting iterators for two separate ranges is also a single logical thought to your algorithm. But I contest this because they're not atomic actions. Getting the beginning of a range is useless without getting its end (in most cases). That's what makes the pair of actions "atomic"; you can't do anything with them until both are complete.

I wholeheartedly disagree. The c++ memory model mandates that getting the two pairs of iterators is indeed atomic. Everything between two memory fences is logically atomic. This is the foundation of the as-if rule. This is unarguable.
 

However, getting one iterator range can be useful without getting some other iterator range. It may not be useful to your algorithm, but it is still useful in the general sense.

Agree. 


That's my personal dividing line.

With the greatest respect, I cannot allow your personal opinions alone to hold back useful evolutions of the language. The fact that you are against the use of auto is very alarming to me.
 

On Thursday, December 14, 2017 at 7:37:22 AM UTC-5, Richard Hodges wrote:
Having pondered the responses, it turns out that I can express my algorithm in a tightly scoped, DRY and legal manner in current c++ like this:

I'm not really sure what that proves. You can accomplish the same thing by using curly braces:

{
  std
::size_t ix = 0;
 
auto first = begin(v);
 
auto last = end(v);

 
for(; first != last; ++first, ++ix)
   
//Actual loop body;
}

You can even remove the `begin` line by adding it to the `for` statement. So what's the downside of this? Do you really need to write these multi-variable things that often that we need a language change? You've not answered that question.

That's a fair question. My initial response is that the extra curly braces are not beautiful. And to me elegance and beauty of code presentation is a factor in my enjoyment of writing it. To me, that's enough of a reason for a change (if not the one I originally proposed). I appreciate that you may not take aesthetics to heart as much as me.
 

I really do think that there is a case for allowing:

    for(auto [i = std::size_t(0)] ; auto&& elem : v)
            f(elem, i++);

or:

    for(auto [i = std::size_t(0), first = begin(v), last = end(v)] ; first != last ; ++first, ++i)
            f(*first, i);

or:

    for(auto i = std::size_t(0), first = begin(v), last = end(v) ; first != last ; ++first, ++i)
            f(*first, i);

since we already have this ability in the nasty lambda above, why not provide the ability in a way that's easy to reason about?

You can already do the first one; just get rid of the structured binding bit. And the second and third ones have nothing to do with one another. They have completely different semantics.

--
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.

adrian....@gmail.com

unread,
Dec 16, 2017, 10:57:31 AM12/16/17
to ISO C++ Standard - Future Proposals
On Thursday, December 14, 2017 at 10:00:45 AM UTC-5, Richard Hodges wrote:
> I'm sure some people do this, but an indexed range view would handle this much more easily and compactly:
>
> for(auto &[i, val] : std::counted_view(rng))
> {...}

There is currently no such thing as a std::counted_view but I take your point. The problems with this approach are:

  1. You have to think of a meaningful name for every trivial range adapter you ever want to write. This is a bigger problem in a large organisation or distributed team that it might at first seem.
  2. Range adapters involve abstracting out one's algorithm into a verbose class. Precisely the opposite of the intent of range-based for (keep simple things simple, keep logic in the loop body)
  3. You have to document and publish what is essentially a line or 2 of trivial self-documenting code.
  4. You have to wait for at least 3 years of squabbling in the isocpp committee before it'll be in the std namespace*
* ok, fair enough, you could submit it to boost and get it out in 3 months if you're happy with boost::counted_view.

It would seem to me that a generic 'view' class could be made for this sort of thing which could be inherited to ease the ability for developers to use this methodology, allowing the creation of more specialized 'view' classes.

adrian....@gmail.com

unread,
Dec 16, 2017, 11:06:21 AM12/16/17
to ISO C++ Standard - Future Proposals
Um, I don't think you can do the first one.  Unless g++7.2.0 isn't complaint yet, the following snippet will not comple:

std::vector<int> v = {1,2,3,4};
for(auto i=0; auto&& e : v)
{
std::cout << e << std::endl;
}
 

Patrice Roy

unread,
Jan 5, 2018, 9:27:11 PM1/5/18
to std-pr...@isocpp.org
FWIW, Richard, I think your idea has merits. I have often found it a bit annoying not to be able to declare variables of different types in the init section of a for statement, and inspiration from lambda capture syntax / structured bindings seems harmonious to me.

Alternatives suggested so far given the existing language would either force manually introducing a scope around the for loop, or accepting unnecessarily long lifetimes for those additional variables; the first one is unpleasant (sort of a hack to get around a limitation), the second one is actually adverse to what would be an appropriate strategy for the management of those variables' lifetimes. Neither is as satisfactory as your idea.

The initialization par of the for(auto [a = 0, b = ""s]; a != N; ++a) { /* ... */ } form can be seen a emulating an implicit make_tuple as you are suggesting, or as initializing a and b from an anonymous struct of sorts. Either works as a mental model for me, and the proposed syntax seems easier to teach and more elegant than what it would replace given that mental model. It's a syntax that, for someone who has played with structured bindings already, just seems very natural (in fact, to me, you just hit a '"why didn't I think of it?" spot :) ).

Are you planning a paper for EWG with this?

Thanks!
  

--
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.

Tony V E

unread,
Jan 5, 2018, 10:03:09 PM1/5/18
to Patrice Roy
We should also keep in mind that pattern matching will need an easy, unobtrusive way to declare variables. Even auto [ ] might be too much. It needs a single char like `x or something.

Sent from my BlackBerry portable Babbage Device
From: Patrice Roy
Sent: Friday, January 5, 2018 9:27 PM
Subject: Re: [std-proposals] Re: Structured Bindings - revisited

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.

Patrice Roy

unread,
Jan 5, 2018, 11:38:45 PM1/5/18
to std-pr...@isocpp.org
Interesting addition to the discussion. I'll wait to see what for it takes, though; Richard's suggestion blends in well with the existing set; it's not offensive in the context of lambda captures and structured bindings, and it's actually teachable. My guess (it's speculative, at this point) is that syntactic upgrades brought upon by an eventual pattern matching proposal being accepted will likely benefit all three, so relative homogeneity seems to me to be the premium factor for the moment.

--
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.

Richard Hodges

unread,
Jan 6, 2018, 2:25:03 AM1/6/18
to std-pr...@isocpp.org
> FWIW, Richard, I think your idea has merits. I have often found it a bit annoying not to be able to declare variables of different types in the init section of a for statement, and inspiration from lambda capture syntax / structured bindings seems harmonious to me.

> Are you planning a paper for EWG with this?

At the end of last year I voiced a few ideas in this forum and std-proposals, based on my experiences and frustrations in 25 years of writing c++.

To be honest, I found the reception somewhat unwelcoming, and I have a busy job in a small firm, so decided to put my efforts elsewhere. 

Happy to revisit when things calm down for me, if there is genuine interest.



Nicolas Lesser

unread,
Jan 6, 2018, 5:47:14 AM1/6/18
to ISO C++ Standard - Future Proposals
You can, just not in C++17. This change was approved at the last meeting, and was merged in the draft C++20 standard.

Nicol Bolas

unread,
Jan 6, 2018, 11:18:16 AM1/6/18
to ISO C++ Standard - Future Proposals
On Friday, January 5, 2018 at 9:27:11 PM UTC-5, Patrice Roy wrote:
FWIW, Richard, I think your idea has merits. I have often found it a bit annoying not to be able to declare variables of different types in the init section of a for statement, and inspiration from lambda capture syntax / structured bindings seems harmonious to me.
 
If this multi-variable syntax is going to happen, I really would rather it happen as a function of `auto` rather than structured bindings. That is, rather than doing:

for(auto [a = 0, b = ""s]; a != N; ++a)

You do:

for(auto a = 0, b = ""s; a != N; ++a)

If you're going to allow a single statement to introduce/deduce variables of multiple types, then that's what you should allow. This is not a form of structured binding, and you don't really want it to be a form of structured binding.

Nicol Bolas

unread,
Jan 6, 2018, 12:14:09 PM1/6/18
to ISO C++ Standard - Future Proposals
I know this post is almost a month old, but I wanted to clarify a misunderstanding that happened here.

I am not "against the use of auto". I am against the mindless use of auto. I'm against the ideology that one should use auto unless it is literally impossible to avoid doing so.

`auto` is a great and useful tool, and I do put it to use. But like any tool, it can be abused. And we should not invent idioms like AAA where mindless use invites such abuse.

`auto` can improve the clarity of code. But misused, it can make code quite inscrutable. And the power of `auto` to make code impenetrable should never be ignored or forgotten. As such, any expansion of its powers must be looked on with at least some hesitancy.


On Thursday, December 14, 2017 at 1:20:12 PM UTC-5, Richard Hodges wrote:
On 14 December 2017 at 18:19, Nicol Bolas <jmck...@gmail.com> wrote:
... right. Which is why I said you should bring it up. I wasn't being sarcastic.

Consider what I just said about having two "independent operations" on the same line. If you're extracting begin/end from a range, then those aren't "two independent operations". They're a single logical thought:

auto beg = rng.begin(), end = rng.end();

When you have something that is conceptually atomic that, by reason of implementation, requires two distinct actions, that is when it is appropriate to stick them in one line.

I think we agree here. Acquiring an index counter and the limits of a range seem to me to be part of the same logical atomic operation. 

Of course, you'll naturally try to say that getting iterators for two separate ranges is also a single logical thought to your algorithm. But I contest this because they're not atomic actions. Getting the beginning of a range is useless without getting its end (in most cases). That's what makes the pair of actions "atomic"; you can't do anything with them until both are complete.

I wholeheartedly disagree. The c++ memory model mandates that getting the two pairs of iterators is indeed atomic. Everything between two memory fences is logically atomic. This is the foundation of the as-if rule. This is unarguable.

When I said "atomic", I did not mean "threadingly atomic". I meant what I said: "conceptually atomic". That is, it is a single, logically indivisible operation.

Calling a function is "conceptually atomic". You cannot call half of a function, or a quarter of a function. You either call it or you don't; regardless of how many individual steps it has, from the perspective of the caller, it is a single indivisible operation.

Similarly, getting the iterators of a range is conceptually atomic. It almost never makes sense to get just the begin iterator or just the end. If you're getting one, then you need the other (or you already have it in some way). The only cases where this isn't the case involve using just a begin iterator because some other range is going to decide where it ends.

And let's be frank: that is a short path to buffer overrun attacks.

By contrast, getting the iterators for two ranges is two separate conceptually atomic operations. The same goes for getting an indexed range (whether you do that as just an integer that you increment or a real range, the idea is the same). Getting an indexed range is a conceptually separate operation, since you can choose to do so or not based on your particular usage needs.

On Thursday, December 14, 2017 at 7:37:22 AM UTC-5, Richard Hodges wrote:
Having pondered the responses, it turns out that I can express my algorithm in a tightly scoped, DRY and legal manner in current c++ like this:

I'm not really sure what that proves. You can accomplish the same thing by using curly braces:

{
  std
::size_t ix = 0;
 
auto first = begin(v);
 
auto last = end(v);

 
for(; first != last; ++first, ++ix)
   
//Actual loop body;
}

You can even remove the `begin` line by adding it to the `for` statement. So what's the downside of this? Do you really need to write these multi-variable things that often that we need a language change? You've not answered that question.

That's a fair question. My initial response is that the extra curly braces are not beautiful. And to me elegance and beauty of code presentation is a factor in my enjoyment of writing it. To me, that's enough of a reason for a change (if not the one I originally proposed). I appreciate that you may not take aesthetics to heart as much as me.

Oh, I do take aesthetics to heart. That's why I disagree with you. We simply have very different ideas about what is "beautiful".

See for me, it doesn't matter how you declare 4 variables in a `for` initializer statement. Because no matter what syntax you use, it's ugly to me based on the fact that you have 4 loop variables.

Beautiful code is hard to get wrong. When you have many loop variables, it's easy to forget to increment the ones that need incrementing. And it's easy to increment the wrong one. It's easy to forget to test one or to test the wrong one.

Beautiful code provides minimal visual clutter. No matter what syntax you use to declare 4 loop variables, I'm still seeing lots of code that has nothing to do with the code inside the loop. I'm seeing a bunch of initializing expressions. I'm seeing a bunch of conditions in the test. I'm seeing a bunch of increments. Etc.

Beautiful code makes the user's intent clear and unequivocal. The more visual clutter there is in a loop, the harder it is to tell what's actually happening. The user's intent is lost.

The goal of C++ features should not be to make ugly code slightly less ugly; it should be to make ugly code beautiful. We should be eliminating or minimizing the desire to have these complex loops, not making them slightly less ugly to write.

Johannes Schaub

unread,
Jan 6, 2018, 2:27:45 PM1/6/18
to std-pr...@isocpp.org
2017-12-13 11:15 GMT+01:00 Richard Hodges <hodg...@gmail.com>:
> A simple example:
>
> Here's a fairly common idiom, expressed in terms of a structured binding:
>
> #include <iostream>
> #include <vector>
> #include <tuple>
>
> int main()
> {
> std::vector<int> v = { 6,5,4,3,2,1 };
>
> for( auto [i, first, last] = std::make_tuple(std::size_t(0), begin(v),
> end(v))
> ; first != last
> ; ++i, ++first)
> {
> std::cout << "index: " << i << "\t" << *first << "\n";
> }
> }
>
> It seems sensible to me to group the loop's variables in a structured
> binding since
>
> it's succinct and,
> they remain firmly local to the loop's scope
> It's the only way to declare multiple dissimilar variables within the for
> statement.
>
>
> However, this code is not DRY - the naming of the bound variables must
> correspond to the creation of the tuple. Maintenance confusion awaits.
>
> It seems to me that a small, non-breaking syntax change could allow this:
>
> for( auto [i = size_t(0), first = begin(v), last = end(v)]
> ; first != last
> ; ++i, ++first)
> {
> // ...
> }
>
> which would:
>
> a) be 100% DRY,
> b) involve less typing
> c) match the syntax for lambda capture (I could expand further on my
> thoughts on that, but perhaps another day)
> d) IMHO be easier to teach
>
> Wouldn't that be better?
>
> I'd value your thoughts.
>

This "normal declaration with initialization" conflates with the
different concept of structured bindings. Also, this syntax IMO is
confusing. It reads to me as:

- Normally, each variable declared in a multi-declaration
declaration-statement must deduce to the same type,
- Except when you wrap "[..]" around everything, then you are allowed
different types.

In addition, "i", "first" and "last" *always* are references with
structured bindings, while users IMO would expect they are not
references if they are declared as "auto [ ...]" ("allow multiple
types"). One understands the reason only when one learns the
completely distinct concept of structured bindings and even then, the
reason won't hold anymore when in this orinary "declaration with
multiple types" case.

Alternative workaround using a feature that floated around in another
thread (lambda-like struct literals.. required because you need to
have access to "v" within the new struct):

for([&] struct { size_t i = 0; auto first = begin(v), second = end(v); } d;
d.first != d.last;
++d.first, ++d.last)
{
// ...
}

This requires "auto" for non-static data members, but in this narrow
use case.. perhaps one could allow that. After all, these initializers
don't require "this" to be a complete type.

Patrice Roy

unread,
Jan 6, 2018, 3:35:50 PM1/6/18
to std-pr...@isocpp.org
Yes, that's an alternative syntax I think would work too. Changing the way auto works as Nicol suggests seems like a bigger change to me, and I would prefer avoiding that until there's a sound rationale and a paper exploring its impact throughout the language (particularly with respect to concepts; it's already quite complicated there).

Thanks!

--
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.

Ville Voutilainen

unread,
Jan 6, 2018, 3:42:33 PM1/6/18
to ISO C++ Standard - Future Proposals
On 6 January 2018 at 22:35, Patrice Roy <patr...@gmail.com> wrote:
> Yes, that's an alternative syntax I think would work too. Changing the way
> auto works as Nicol suggests seems like a bigger change to me, and I would
> prefer avoiding that until there's a sound rationale and a paper exploring
> its impact throughout the language (particularly with respect to concepts;
> it's already quite complicated there).
>
> Thanks!

Perhaps we should just do

for(idx_iter d = v;
d.first != d.last;
++d.first, ++d.i)
{
// ...
}

That is, standardize a library type that can initialize itself from a
container, and provides access to an index and the pair of iterators.

Patrice Roy

unread,
Jan 6, 2018, 5:03:39 PM1/6/18
to std-pr...@isocpp.org
I think it might be useful, but would not cover the use-cases brought to light by Richard in his initial post.

--
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.

Nicol Bolas

unread,
Jan 6, 2018, 10:22:50 PM1/6/18
to ISO C++ Standard - Future Proposals
On Saturday, January 6, 2018 at 3:35:50 PM UTC-5, Patrice Roy wrote:
Yes, that's an alternative syntax I think would work too. Changing the way auto works as Nicol suggests seems like a bigger change to me,
 
A bigger change? The `auto` change requires nothing more than removing half of a sentence from the standard (in [dcl.spec.auto]/7). That's a much smaller change than introducing a type declaration. In terms of compiler implementation, it requires removing the check that [dcl.spec.auto]/7 requires. Again, far less work than introducing an entirely new grammatical construct.
 
and I would prefer avoiding that until there's a sound rationale and a paper exploring its impact throughout the language (particularly with respect to concepts; it's already quite complicated there).

Concept interaction with `auto` declarations would be no more complicated for multiple variables than for the member variables inside your "lambda struct".

Jake Arkinstall

unread,
Jan 6, 2018, 11:07:35 PM1/6/18
to std-pr...@isocpp.org
I agree. From my perspective, allowing multiple auto types in one statement is a significantly cleaner option - especially when, from what I can tell, the purpose of the other option is being able to work around the fact that multiple auto types aren't allowed.

--
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.

mihailn...@gmail.com

unread,
Jan 7, 2018, 7:48:37 AM1/7/18
to ISO C++ Standard - Future Proposals
Now it seems the best time to investigate the possibility for auto to being able to declare multiple, comma separated, variables.

With the new init if, and the new init range-for - we should be able to do as much work as possible in a single statement!


It looks to me the disallow is just legacy "compatibility" mode - to let auto be optional. 
I also can't imagine how this can break existing code, as there is no conversion/promotion right now - the types must be exactly the same - but as said, this must be investigated.


Can someone write a paper, if one is not already written, to get the ball rolling? Nicol Bolas? Giovanni Piero Deretta?
I read, this was "voted against", but that was 10+ years ago, I imagine, and it is perfectly OK to be conservative on the first iteration of the feature (let auto be optional),
but as I said, now is the best time to re-investigate the issue! At last we should have paper and hear some arguments against, because I can't think of any deal-breaking. 

Nicol Bolas

unread,
Jan 7, 2018, 10:12:29 AM1/7/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
On Sunday, January 7, 2018 at 7:48:37 AM UTC-5, mihailn...@gmail.com wrote:
Now it seems the best time to investigate the possibility for auto to being able to declare multiple, comma separated, variables.

With the new init if, and the new init range-for - we should be able to do as much work as possible in a single statement!

Long statements are not clear statements. The more a statement does, the less easy it is to figure out what it's doing.

We didn't add initializers to `if`, `switch`, and range `for` to make them "do as much work as possible". It was done to make code less brittle ("solving" the temporary range issue) or to deal with cases where you need to compute a value, test if its valid, and then use it in an `if` statement.

This is the principle downside to me of allowing variables of different types in `auto`: that it will encourage people to declare every variable in a block in one statement, even when it isn't necessary.
It looks to me the disallow is just legacy "compatibility" mode - to let auto be optional.

`auto` is optional; adding this will not make it any less optional. I don't know what you mean by this.
 
I also can't imagine how this can break existing code, as there is no conversion/promotion right now - the types must be exactly the same - but as said, this must be investigated.


Can someone write a paper, if one is not already written, to get the ball rolling? Nicol Bolas? Giovanni Piero Deretta?

I'm not going to do it because I genuinely don't really want to see this happen. See the above for reasons why.

But if some form of multiple deduction is going to happen, then this is the correct form for it to take (rather than (ab)using "structured binding" syntax).

Patrice Roy

unread,
Jan 7, 2018, 11:19:21 AM1/7/18
to std-pr...@isocpp.org
I don't think it's a matter of sentence length in the standard text. It's more a matter of :

int a = 2, b = 3; // same type
auto x = "hello"s, y = 3.14159; // distinct types, quite new; impact on the way people write C++? Requires thought, at the very least
// ...
template <Regular T> void f(T, T); // same type
void g(Regular, Regular); // if short syntax eventually gets in : same type? different types?
void h(auto a, auto b); // same rules as Regular,Regular or not? Papers have been written debating both options

If you see single-auto, multiple types as the way to go, I think it's a position that deserves a paper. It does not jump as being to path to follow in a self-evident manner; maybe you'll find a convincing angle, and I for one will happily read it once it's written. The alternatives seem less contentious to me, given prior art, but I'm open to examining what you have in mind once it's been put in proposal form.




--
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.

mihailn...@gmail.com

unread,
Jan 7, 2018, 11:25:19 AM1/7/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com


On Sunday, January 7, 2018 at 5:12:29 PM UTC+2, Nicol Bolas wrote:
On Sunday, January 7, 2018 at 7:48:37 AM UTC-5, mihailn...@gmail.com wrote:
Now it seems the best time to investigate the possibility for auto to being able to declare multiple, comma separated, variables.

With the new init if, and the new init range-for - we should be able to do as much work as possible in a single statement!

Long statements are not clear statements. The more a statement does, the less easy it is to figure out what it's doing.

We didn't add initializers to `if`, `switch`, and range `for` to make them "do as much work as possible". It was done to make code less brittle ("solving" the temporary range issue) or to deal with cases where you need to compute a value, test if its valid, and then use it in an `if` statement. 

This is the principle downside to me of allowing variables of different types in `auto`: that it will encourage people to declare every variable in a block in one statement, even when it isn't necessary.

I will rephrase - with init-if and init-range-for, this feature will have even more uses. And by uses, I mean legitimate ones.

If a variable is used in scope only and it must be created before the if/for/switch statement, then the init section is its best place.
I also don't consider comma separated list of variables declarations worse then line by line ones.

And one more thing - people are already doing it the ugly way!

auto i = std::size_t(0), first = begin(v), last = end(v)

will prevent writing this

auto [i, first, last] = std::make_tuple(std::size_t(0), begin(v), end(v))

which we both agree is worse!
 
It looks to me the disallow is just legacy "compatibility" mode - to let auto be optional.

`auto` is optional; adding this will not make it any less optional. I don't know what you mean by this.

Humm, you mean that we simply allow comma separated list to evaluate to different variable types?

Because otherwise, if this is added, auto is no longer optional, like it is today.

auto i=2, j=3; //< 'auto' is optional, can be replaced with int

auto s="hi"s, i=1; //< auto is not optional, code will break if changed to anything else
 
I really doubt anyone will have a problem with the fact auto is not optional, though.

Jake Arkinstall

unread,
Jan 7, 2018, 11:53:58 AM1/7/18
to std-pr...@isocpp.org


On 7 Jan 2018 16:19, "Patrice Roy" <patr...@gmail.com> wrote:
I don't think it's a matter of sentence length in the standard text. It's more a matter of :

int a = 2, b = 3; // same type
auto x = "hello"s, y = 3.14159; // distinct types, quite new; impact on the way people write C++? Requires thought, at the very least

Depends. It seems more natural to me.

int a = 1, b = 2, c = 3; //a,b,c all ints
auto x = 4, y = "thing"s, z = std::make_shared<thing>(param); // x, y, z all automatically deducted.

It isn't the first big difference either:

auto a=1, b=1.2; // compiler error
int a=1, b=1.2; // implicit cast used.

As such, just one more distinction between the two is reasonable. As far as I'm concerned they're different statements, and holding onto similarities for similarity's sake, when there are already big enough distinctions, is an unnecessary hurdle. 

Ville Voutilainen

unread,
Jan 7, 2018, 12:19:16 PM1/7/18
to ISO C++ Standard - Future Proposals
On 7 January 2018 at 18:19, Patrice Roy <patr...@gmail.com> wrote:
> template <Regular T> void f(T, T); // same type
> void g(Regular, Regular); // if short syntax eventually gets in : same type?
> different types?
> void h(auto a, auto b); // same rules as Regular,Regular or not? Papers have
> been written debating both options

I haven't seen a paper suggesting that h should have two parameters of
the same type,
can you point me to one?

Nicol Bolas

unread,
Jan 7, 2018, 12:23:43 PM1/7/18
to ISO C++ Standard - Future Proposals
On Sunday, January 7, 2018 at 11:19:21 AM UTC-5, Patrice Roy wrote:
I don't think it's a matter of sentence length in the standard text. It's more a matter of :

int a = 2, b = 3; // same type
auto x = "hello"s, y = 3.14159; // distinct types, quite new; impact on the way people write C++? Requires thought, at the very least
// ...
template <Regular T> void f(T, T); // same type
void g(Regular, Regular); // if short syntax eventually gets in : same type? different types?
void h(auto a, auto b); // same rules as Regular,Regular or not? Papers have been written debating both options

If you see single-auto, multiple types as the way to go, I think it's a position that deserves a paper. It does not jump as being to path to follow in a self-evident manner;

Why not?

Why is this "self-evident":

auto [i = std::size_t(0), first = begin(v), last = end(v)];

When this is not "self-evident":

auto i = std::size_t(0), first = begin(v), last = end(v);

Literally the only difference between them is the presence of two brackets. What are those brackets doing that makes the former code more readable and reasonable than the latter?

Those brackets usually mean "structured binding". But you're not doing structured binding. You're not decomposing a type; you're creating new variables.

maybe you'll find a convincing angle, and I for one will happily read it once it's written. The alternatives seem less contentious to me, given prior art,

What prior art?

Nicol Bolas

unread,
Jan 7, 2018, 12:25:09 PM1/7/18
to ISO C++ Standard - Future Proposals
Equally importantly, `[](auto a, auto b)` already doesn't do this. So any such paper is going to have a hell of a mountain to climb to explain that inconsistency. 

Ville Voutilainen

unread,
Jan 7, 2018, 12:29:53 PM1/7/18
to ISO C++ Standard - Future Proposals
Which is why I proposed allowing 'void f(auto, auto);' and suggested
that the committee
has already made its bed on it. Various people claimed they wish to
see what the outcome
of the terse syntax discussion is; they remain mistaken.

Nicol Bolas

unread,
Jan 7, 2018, 12:39:07 PM1/7/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
On Sunday, January 7, 2018 at 11:25:19 AM UTC-5, mihailn...@gmail.com wrote:
On Sunday, January 7, 2018 at 5:12:29 PM UTC+2, Nicol Bolas wrote:
On Sunday, January 7, 2018 at 7:48:37 AM UTC-5, mihailn...@gmail.com wrote:
Now it seems the best time to investigate the possibility for auto to being able to declare multiple, comma separated, variables.

With the new init if, and the new init range-for - we should be able to do as much work as possible in a single statement!

Long statements are not clear statements. The more a statement does, the less easy it is to figure out what it's doing.

We didn't add initializers to `if`, `switch`, and range `for` to make them "do as much work as possible". It was done to make code less brittle ("solving" the temporary range issue) or to deal with cases where you need to compute a value, test if its valid, and then use it in an `if` statement. 

This is the principle downside to me of allowing variables of different types in `auto`: that it will encourage people to declare every variable in a block in one statement, even when it isn't necessary.

I will rephrase - with init-if and init-range-for, this feature will have even more uses. And by uses, I mean legitimate ones.

If a variable is used in scope only and it must be created before the if/for/switch statement, then the init section is its best place.
I also don't consider comma separated list of variables declarations worse then line by line ones.

And one more thing - people are already doing it the ugly way!

auto i = std::size_t(0), first = begin(v), last = end(v)

will prevent writing this

auto [i, first, last] = std::make_tuple(std::size_t(0), begin(v), end(v))

which we both agree is worse!

If people are writing that, then they deserve to write it. That's my feeling on the matter. Let's make ugly code as ugly as possible, so that people will switch to the better code.

Turning this:

for( auto [i, first, last] = std::make_tuple(std::size_t(0), begin(v), end(v));

  first
!= last;
 
++i, ++first)
{
 std
::cout << "index: " << i << "\t" << *first << "\n";
}

into this:

for( auto i = std::size_t(0), first = begin(v), last = end(v);

  first
!= last;
 
++i, ++first)
{
 std
::cout << "index: " << i << "\t" << *first << "\n";
}

doesn't really help a whole lot. The latter has 80% of the ugliness of the former. We should encourage people to build tools that allow them to write this instead:

for(auto &[i, val] : std::counted_view(rng))
{

 std
::cout << "index: " << i << "\t" << val << "\n";
}

It's shorter. It's nearly impossible to get wrong. It makes it clear what's going on. Etc.

Most of the "legitimate uses" of this feature can be rewritten in better ways. The few cases that can't be rewritten? Well, I can live with them.

It looks to me the disallow is just legacy "compatibility" mode - to let auto be optional.

`auto` is optional; adding this will not make it any less optional. I don't know what you mean by this.

Humm, you mean that we simply allow comma separated list to evaluate to different variable types?
Because otherwise, if this is added, auto is no longer optional, like it is today.

auto i=2, j=3; //< 'auto' is optional, can be replaced with int

auto s="hi"s, i=1; //< auto is not optional, code will break if changed to anything else
 

I think your "optional" terminology here is confusing. It's more that it is "replaceable"; what you're talking about is the fact that there is some type that you can always substitute in for `auto` declarations and get the same effect.

Yes, this syntax would remove the replicability of auto in such declarations.
Message has been deleted

mihailn...@gmail.com

unread,
Jan 7, 2018, 12:50:35 PM1/7/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com


On Sunday, January 7, 2018 at 7:40:45 PM UTC+2, mihailn...@gmail.com wrote:

On Sunday, January 7, 2018 at 6:19:21 PM UTC+2, Patrice Roy wrote:
...

If you see single-auto, multiple types as the way to go, I think it's a position that deserves a paper. It does not jump as being to path to follow in a self-evident manner; maybe you'll find a convincing angle, and I for one will happily read it once it's written. The alternatives seem less contentious to me, given prior art, but I'm open to examining what you have in mind once it's been put in proposal form.


Prior art here is actually C and predates the new funky stuff (which has it own place).

It is worth noting we already can declare variables of different types:

int i, *pi;

these two are radically different types.

And it gets "better"

int i, *p, a[12], f(), (*pf)(double), C::*mf;


It is painfully obvious here, it is allowed to be declared anything on a single line as long as it can be

...parsed from int. However int-parsable have no semantic meaning. C++ can improve that, fix the age old (technical) limitation, as it was never semantically unwise or forbidden to have multiple variables of different types declared on one line, we just didn't have the tools to express that completely, only partially. 

Jake Arkinstall

unread,
Jan 7, 2018, 12:51:38 PM1/7/18
to std-pr...@isocpp.org
On 7 Jan 2018 17:40, <mihailn...@gmail.com> wrote:
int i, *p, a[12], f(), (*pf)(double), C::*mf;

This reply should have been accompanied by a health warning!

Seriously, though, it's a great example. I honestly hope I never see it in the wild, but it serves a good purpose.


Patrice Roy

unread,
Jan 7, 2018, 3:24:25 PM1/7/18
to std-pr...@isocpp.org
I might have been imprecise; http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0691r0.pdf in section "Abbreviated function syntax support for both “same” and “separate" discussed the case of f(C a,C b) for some concept C and the fact that not everyone agreed as to whether a and b had to be the same type or could be two distinct types as long as both respected concept C. Should it be decided that a and b would need to be of the same type, the discrepancy with f(auto a, auto b) behavior was given as an example. I'm not sure there exists a paper defending a position such that f(auto a, auto b) would ask of a and b to be of the same type; and that was not what I had in mind when writing that short statement. Thanks for the opportunity to make this clearer.

--
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.

Patrice Roy

unread,
Jan 7, 2018, 3:27:18 PM1/7/18
to std-pr...@isocpp.org
With respect to "what prior art", I had the lambda capture syntax and the structured binding syntax, both of which strongly resemble the original proposition in this thread. I understand the interest for the multiple types, single auto declaration, but I don't see an existing case such as this one currently.

The example provided by Jake with an implicit cast does not affect the object's type and as such, does not seem like an existing case to me.

--
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.

Patrice Roy

unread,
Jan 7, 2018, 3:28:21 PM1/7/18
to std-pr...@isocpp.org
Agreed; I merely stated that options were debated, Thanks!

--
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.

mihailn...@gmail.com

unread,
Jan 7, 2018, 5:35:59 PM1/7/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com


On Sunday, January 7, 2018 at 7:39:07 PM UTC+2, Nicol Bolas wrote:
...

And one more thing - people are already doing it the ugly way!

auto i = std::size_t(0), first = begin(v), last = end(v)

will prevent writing this

auto [i, first, last] = std::make_tuple(std::size_t(0), begin(v), end(v))

which we both agree is worse!

If people are writing that, then they deserve to write it. That's my feeling on the matter. Let's make ugly code as ugly as possible, so that people will switch to the better code.

You are assuming all multiple declarations, written in one line, are bad. But that is subjective, considering the trade offs, and they are trade offs - either dummy scopes or some new special classes and functions to fit in the current model.

Consider a real-world, looping over two images pixels:

for(auto* dst_p = dst_start, *src_p = src_start; //< only works if the data is the same type
  dst_p < dst_end;
  dst_p += Bpp, src_p += Bpp)
{
 // use  
}

How is this bad or unclear code? Why should I use special tricks, or extra scopes, or leak variables? We could argue "you must create a view", but what if there is performance penalty (compile- and/or runtime). What if it is an established codebase and/or style?

Why should be able to easily write loops with one variable, but not with more then one, with a different type?!? That limitation is arbitrary, technical, not semantic, not even readability one.  

And we only got for loop so far. Now even more needs will arise (with the range-for, if and switch), uses, which make sense, and make sense iff the variables are needed before the test and are excusive for the scope
- the very same rules C++ applied to the counter variable in the original C for.


Patrice Roy

unread,
Jan 7, 2018, 6:38:56 PM1/7/18
to std-pr...@isocpp.org
Small addition : the mention that something like

int a = 3, *p = &a, &r = a;

... stands as prior art is interesting, and I think it should be part of an eventual proposal.  In this specific statement, auto would probably not cause much harm, but I think that

char c = 65;
auto a = 3, *p = &c;

... which would have been an error previously but would pass with a multi-type, single auto declaration deserves attention (might not be a bad thing, but is definitely new and deserving of attention). Thanks in advance to Nicol or whomever will write that paper.

Cheers!

--
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.

Nicol Bolas

unread,
Jan 7, 2018, 6:55:28 PM1/7/18
to ISO C++ Standard - Future Proposals
On Sunday, January 7, 2018 at 6:38:56 PM UTC-5, Patrice Roy wrote:
Small addition : the mention that something like

int a = 3, *p = &a, &r = a;

... stands as prior art is interesting, and I think it should be part of an eventual proposal.

If you're trying to convince someone that multiple declaration deductions should deduce different types, you probably shouldn't use an example that's confusing even without deduction. That's basically like admitting that you know this proposal opens the door to terrible, illegible, and inscrutable code, but let's do it anyway.

The best argument for this ability is the iterator/sentinel paradigm. There's an undeniable reason why you would need them to be in the same declaration statement. And with the Range TS, the begin/end types are (or can be) different. We even changed how range-based `for` worked to deal with this fact.

The iterator/sentinel paradigm is a far better example. At least in that case, it's clear what's actually going on.

Patrice Roy

unread,
Jan 7, 2018, 7:31:27 PM1/7/18
to std-pr...@isocpp.org
I was just following the suggestion of another contributor to this thread. I maintain you should write a proposal, and I think this deserves attention. You write well, I'm sure it will be a good proposal.

Thanks!

--
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.

Nicol Bolas

unread,
Jan 8, 2018, 12:40:09 AM1/8/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
On Sunday, January 7, 2018 at 5:35:59 PM UTC-5, mihailn...@gmail.com wrote:
On Sunday, January 7, 2018 at 7:39:07 PM UTC+2, Nicol Bolas wrote:
...

And one more thing - people are already doing it the ugly way!

auto i = std::size_t(0), first = begin(v), last = end(v)

will prevent writing this

auto [i, first, last] = std::make_tuple(std::size_t(0), begin(v), end(v))

which we both agree is worse!

If people are writing that, then they deserve to write it. That's my feeling on the matter. Let's make ugly code as ugly as possible, so that people will switch to the better code.

You are assuming all multiple declarations, written in one line, are bad.

No, I'm assuming that enough of them are bad, and enough of them are sufficiently bad, to counter the number that aren't bad.

But that is subjective, considering the trade offs, and they are trade offs - either dummy scopes or some new special classes and functions to fit in the current model.

"New special classes and functions" that we're already getting. And which make the code much more readable. Let's not forget those parts.

Yes, coding style is somewhat subjective. But that doesn't mean you can just dismiss criticism of an idea from the grounds of creating bad code. We shouldn't add features that we know will be misused, unless the absence of those features makes the language significantly worse or eliminates vital use cases.

I've never seen someone actually argue that manually written loops are a good thing. It's usually that manually written loops are sometimes something you have to do, but I don't think I've seen people say that they're genuinely better code than the alternative.

Consider a real-world, looping over two images pixels:

for(auto* dst_p = dst_start, *src_p = src_start; //< only works if the data is the same type
  dst_p < dst_end;
  dst_p += Bpp, src_p += Bpp)
{
 // use  
}

How is this bad or unclear code?

It's "bad or unclear" in almost every way that was used to justify ranged-based `for` in the first place (and many algorithms before that):

1) It's fragile. That loop is an avalanche of DRY violations. Mistype one variable in the wrong place, use `+=` with the wrong increment, and the loop breaks in a way that's not immediately obvious.
1a) Since the loop variables are visible to the `// use` code, it can modify them and thus accidentally break them.
2) It's verbose.
3) It introduces variables that aren't actually used. After all, the `// use` part doesn't care about `src_p` and `dst_p`. It will never modify those variables; it only uses what they point to.

That's not to say that this is utterly irredeemable or terrible code. But it's hardly idiomatic, modern C++, is it? Why would you rather have an easier way to write this code than to have a `std::transform` equivalent that could be made to work with what you're doing? An algorithm approach will be more readable.

And we only got for loop so far. Now even more needs will arise (with the range-for, if and switch), uses, which make sense, and make sense iff the variables are needed before the test and are excusive for the scope

OK, let's consider that. When exactly would you write an `if` statement where you declare 2 variables of different types, generated through different expressions, both of which are needed for the actual conditional test, and both of which are scoped to the condition statement?

And what does such a condition statement look like? What percentage of it is just initializing variables, and what percentage of it is the actual condition itself? Because if your code looks like this:

if(auto big = bag, of = initializer, expression = that, run = on, fore = ver;
  big
== of)
{ ... }

Then I think that would look much better as:

{
 
auto big = bag;
 
auto of = initializer;
 
auto expression = that;
 
auto fore = ver;

 
if(big == of)
 
{ ... }
}

If you need that many variables, then those variables are probably pretty important to what's going on in the code inside the `{...}` part. By breaking them out of the `if` statement, you put some focus on them. It also makes the condition itself small enough that it's clear what the condition itself is actually testing. That makes the code more maintainable, since you can read the condition itself more easily.

Range-based `for` would have some use for this feature, primarily for doing zip-range gymnastics across multiple temporary ranges. But every such range you use this with reduces the difference between this:

for(auto foo1 = get_range1(), foo2 = get_range2(), foo3 = get_range3();
 
auto &[f1, f2, f3] : zip_range(foo1, foo2, foo3))

and this:

{ auto foo1 = get_range1(); auto foo2 = get_range2(); auto foo3 = get_range3();
 
for(auto &[f1, f2, f3] : zip_range(foo1, foo2, foo3))
}

In a perfect world, we would have the ability to explicitly extend the lifetime of a temporary in some way, and thus we wouldn't need to name these ranges at all.

Nicol Bolas

unread,
Jan 8, 2018, 12:41:35 AM1/8/18
to ISO C++ Standard - Future Proposals
On Sunday, January 7, 2018 at 7:31:27 PM UTC-5, Patrice Roy wrote:
I was just following the suggestion of another contributor to this thread. I maintain you should write a proposal, and I think this deserves attention. You write well, I'm sure it will be a good proposal.
 
You don't quite seem to get this: I don't particularly want to see anything from this thread actually happen. I'm content with the status quo, with regard to variable declarations (well, I'd like to see some changes to structured binding, but not in this direction).

My position is merely that, if we're going to have a way to declare multiple variables with multiple deduced types in one statement, then it should be spelled the obvious way: with `auto`. Just `auto`; no meaningless brackets.

This is better in every respect than any structured binding solution. It's syntactically better (the syntax already exists; it just is forbidden). It's semantically better (you're using the syntax that already means "create multiple variables via deduction"; it just so happens to be deducing different types now). It's better standard-wise (removing half of a sentence vs. creating entire new grammar rules). And it's easier on implementations (removing the code that prevents a scenario you can already parse vs. adding new grammar parsing code).

That it is better than structured binding solutions does not mean I think it's a good idea. I merely consider it the correct way to implement functionality I don't want to see provided.

Nicolas Lesser

unread,
Jan 8, 2018, 1:52:13 AM1/8/18
to ISO C++ Standard - Future Proposals
I also find this proposal, and the proposal to use auto without brackets a not so good one.

As you said, defining multiple variables in a single line is not very pretty, and in fact, the CppCoreGuidelines discourage it too:

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Res-name-one

Patrice Roy

unread,
Jan 8, 2018, 3:06:58 AM1/8/18
to std-pr...@isocpp.org
That's a different story altogether, then. Thanks for clarifying your position.

--
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.

mihailn...@gmail.com

unread,
Jan 8, 2018, 3:20:17 AM1/8/18
to ISO C++ Standard - Future Proposals
Nicolas, the point is we already have multiple variable declaration without brackets and they already support different types (see above)
- the Right Thing is to stick with that if are to be used at all. And in some places they are needed because if alternatives are considered inferior by the programmer (which is subjective)

Nicol, I agree with most of your points, including about using a view. However I don't agree that

{
  auto a = geta();
  b = getb();
  if(hum(a, b))
  {
    // use a and b
  }
}

Is better then.

if(auto a=geta(), b=getb(); hum(a, b))
{
  // use a and b
}

Also your example about big bag is not fair as it does not follow the rule when to consider single line init - the variable to be used both in test and in scope

if(auto big = bag;
   big == initializer)
 // 'big' used in test and scope ('initializer' (aka 'of') not used in scope, no need for a variable!)

 // declare 'expression' and 'fore' here!!!
}

if(auto big = bag, of = initializer;
    big == of)
{
  // both 'big' and 'of' used in test and scope

 // declare 'expression' and 'fore' here!!!
}


Lastly, I also don't like the assumption we know all the programmer needs in terms of variables and types, used for his code! 

Jake Arkinstall

unread,
Jan 8, 2018, 4:01:53 AM1/8/18
to std-pr...@isocpp.org


On 8 Jan 2018 05:40, "Nicol Bolas" <jmck...@gmail.com> wrote:

3) It introduces variables that aren't actually used. After all, the `// use` part doesn't care about `src_p` and `dst_p`. It will never modify those variables; it only uses what they point to.

That's an assumption. For the specific 2d image iteration it very well might be the case but there are situations in data processing in which it is not. For example, operations/observations on local windows of data. This is not uncommon.

For me, any "solution" which removes my access to a range of N elements before the one explicitly pointed to by the loop iterator is useless.

for(
    auto s_first = cbegin(source),
             s_last = s_first + N,
             dest = begin(destination);
    s_last != cend(source);
    ++s_first, ++s_last, ++dest
){
    dest->some_op(s_first, s_last);
}

inkwizyt...@gmail.com

unread,
Jan 8, 2018, 9:21:07 AM1/8/18
to ISO C++ Standard - Future Proposals
I would prefer it to look like:
for (auto& [range, dest] : zip(range_window(source, N), destination))
{
   dest
.some_op(range.begin(), range.end());
}


Jake Arkinstall

unread,
Jan 8, 2018, 10:39:55 AM1/8/18
to std-pr...@isocpp.org
That makes sense in this situation. My concern is that there are arbitrary operations with pointers and wrapping them in functions isn't always the preferable solution. The general solution is:

{
     initialize
     while(condition){
          body
          increment
     }
}

And this is exactly how

for(initialize; condition; increment){
     body
}

is defined, except that we are limited to one statement for "initialize". Making that one statement a bit more powerful in a way which breaks no old code, and is just as easy to read, is a benefit. Sure we can come up with an entire zoo of handy views and sub-functions to get this same functionality, but I wouldn't always call that preferable.
Message has been deleted

mihailn...@gmail.com

unread,
Jan 8, 2018, 11:20:22 AM1/8/18
to ISO C++ Standard - Future Proposals
Ironically examples like this only prove the need for multiple variables declaration - range and dest are two variables, both of different type.

Sure it is better written that way, but can it be *always* written like that? Nicol Bolas will say Yes, but I doubt.
Sometimes there might be a need for more control over the variables (or the iteration), sometimes it might cumbersome to wrap everything in pretty range interfaces.

Manual variable initialization will always have its place.


And as I said, it is not just for. Other use case are bound to come:

if(auto lock = std::lock_guard(mtx)
   , queue = getQueue();
   !queue.empty())
{
   // use queue
}

if(auto a = weakA.lock()
   , b = weakB.lock();
   a && b)

{
  // use a and b
}

These are pretty reasonable.
Sooner or later the one variable or multiple with the same type limitations will become too limiting, simply because they don't have any semantic sense.
At that point people will just start using hacks (like the tuple one).
 

 

Marcin Jaczewski

unread,
Jan 8, 2018, 11:22:26 AM1/8/18
to std-pr...@isocpp.org
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/lk5oLy6qM7Q/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.

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

You are right that view are not silver bullet that will fix all possible problems and in the and we will need use old `for` for some cases. Question is how many times you have case that using some standard view or writing new one (even if it will be use only one time) will be inappropriate? Exactly where line is in many times depend on taste but overall we could agree on order of magnitude.
How often you need have to write this loops that can not be replaces by view? If it will happens very often then indeed this change could improve overall langue but if is not? Then I will leave this as is.

BTW in `for` the `initialize` is equally powerful as `increment`, you can assign multiple variables in statement, only problem is that allow only one declaration statement. In old C this was not problem because it was forbidden to declare variables in the middle of function.

inkwizyt...@gmail.com

unread,
Jan 8, 2018, 11:58:58 AM1/8/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com


On Monday, January 8, 2018 at 5:20:22 PM UTC+1, mihailn...@gmail.com wrote:


On Monday, January 8, 2018 at 4:21:07 PM UTC+2, Marcin Jaczewski wrote:


On Monday, January 8, 2018 at 10:01:53 AM UTC+1, Jake Arkinstall wrote:


On 8 Jan 2018 05:40, "Nicol Bolas" <jmck...@gmail.com> wrote:

3) It introduces variables that aren't actually used. After all, the `// use` part doesn't care about `src_p` and `dst_p`. It will never modify those variables; it only uses what they point to.

That's an assumption. For the specific 2d image iteration it very well might be the case but there are situations in data processing in which it is not. For example, operations/observations on local windows of data. This is not uncommon.

For me, any "solution" which removes my access to a range of N elements before the one explicitly pointed to by the loop iterator is useless.

for(
    auto s_first = cbegin(source),
             s_last = s_first + N,
             dest = begin(destination);
    s_last != cend(source);
    ++s_first, ++s_last, ++dest
){
    dest->some_op(s_first, s_last);
}

I would prefer it to look like:
for (auto& [range, dest] : zip(range_window(source, N), destination))
{
   dest
.some_op(range.begin(), range.end());
}



Ironically examples like this only prove the need for multiple variables declaration - range and dest are two variables, both of different type.

Technically this is only one variable with two properties, but this is detail.
 
Sure it is better written that way, but can it be *always* written like that? Nicol Bolas will say Yes, but I doubt.
Sometimes there might be a need for more control over the variables (or the iteration), sometimes it might cumbersome to wrap everything in pretty range interfaces.

Manual variable initialization will always have its place.

Yes, but how often it will be needed? If 99% loops are handled by views and 1% by classic `for`, then it will be still worth change language for them?
 


And as I said, it is not just for. Other use case are bound to come:

if(auto lock = std::lock_guard(mtx)
   , queue = getQueue();
   !queue.empty())
{
   // use queue
}

if(auto a = weakA.lock()
   , b = weakB.lock();
   a && b)
{
  // use a and b
}

These are pretty reasonable.
Sooner or later the one variable or multiple with the same type limitations will become too limiting, simply because they don't have any semantic sense.
At that point people will just start using hacks (like the tuple one).
 
But at some point I think that lot better would be if this `if` was split, because too much happens after `if(`. Much better would be in some cases current solution:
{
   
auto a = weakA.lock();
   
auto b = weakB.lock();
   
if(a && b)

   
{
       
// use a and b
   
}
}
I consider new `if` syntax is for trivial cases, not for all possible initializations. If `if` initialization do not fit one line than it should not have any initialization.

Jake Arkinstall

unread,
Jan 8, 2018, 12:31:47 PM1/8/18
to std-pr...@isocpp.org


On 8 Jan 2018 16:22, "Marcin Jaczewski" <marcinja...@gmail.com> wrote:
You are right that view are not silver bullet that will fix all possible problems and in the and we will need use old `for` for some cases. Question is how many times you have case that using some standard view or writing new one (even if it will be use only one time) will be inappropriate? Exactly where line is in many times depend on taste but overall we could agree on order of magnitude.
How often you need have to write this loops that can not be replaces by view? If it will happens very often then indeed this change could improve overall langue but if is not? Then I will leave this as is.

Actually, my concern is about maintainability. Okay, sure, iterator-based approaches aren't always pretty, but they're very flexible and easy to modify without much thought. How would the provided example change, for example, if I wanted to start the iteration through the destination collection at N elements after the beginning? At least another function or another view on the destination collection - vs just "+ N" with iterators.

Sure, I understand that we are moving towards a more poetic, almost pythonic, approach to loops, but sometimes the iterator approach is useful specifically because you can do arbitrary non-trivial things with them. I'm in favour of allowing prettier ways of doing things as much as the next guy, but there are always going to be some cases where the iterator approach is prettier than using combinations of functions and views. Giving those more power, again without breaking changes, doesn't seem like something that should need this amount of defending.
Reply all
Reply to author
Forward
0 new messages