range-based-for with temporaries

142 views
Skip to first unread message

Aarón Bueno Villares

unread,
Oct 6, 2017, 4:55:20 PM10/6/17
to ISO C++ Standard - Discussion
Shouldn't something like this allowed?

    std::list<a_big_struct_t> f()
    using result_t = std::map<key, a_big_struct_t>;
    
    result_t computate_something()
    {
        result_t res;

        for (auto&& obj : f())
             res[computate_key(obj)] = std::move(obj);

        return res;
    }

You could of course save the result of f in a variable before the loop and then move anyway, but the semantic of getting the contents of a temporary container, its equivalent to:

    auto&& field = object_factory().field; 

because subobjects of temporaries are xvalues.

Since the range-based-for creates a reference before the loop starts, the temporary container has "for-scope", and the object is alive, you can safely take references to the iterator's contents and besides its semantics will match the usual ones of object and array prvalues.

Nicol Bolas

unread,
Oct 6, 2017, 6:51:42 PM10/6/17
to ISO C++ Standard - Discussion
On Friday, October 6, 2017 at 4:55:20 PM UTC-4, Aarón Bueno Villares wrote:
Shouldn't something like this allowed?

... it is allowed. The return value of the range expression is stored in an `auto&&` variable. If the expression is a prvalue, it will manifest a temporary, and the variable will extend the lifetime of that temporary for the duration of that loop.


Aarón Bueno Villares

unread,
Oct 6, 2017, 7:34:40 PM10/6/17
to ISO C++ Standard - Discussion
Sorry I write too fast and used auto accidentally.

Change:

    for (auto&& obj : f())


by

    for(a_big_struct_t&& obj : f())


and it doesn't compile because the returned list is transparently a lvalue.

My question is if that makes sense to be allowed.
Message has been deleted

Aarón Bueno Villares

unread,
Oct 6, 2017, 7:44:44 PM10/6/17
to ISO C++ Standard - Discussion
Maybe the range-for-loop equivalence must be replaced by something like (in the containers case for example):

{
    
auto && __range = range_expression ; 
    
auto __begin = std::forward<decltype(__range)>(__range).begin();
    
auto __end = std::forward<decltype(__range)>(__range).end();

    
for ( ; __begin != __end; ++__begin) { 
       range_declaration 
= *__begin; 
       loop_statement 
    
}
} 

To allow a correct handling of ref-qualified overloads. That wouldn't work with standard containers currently because they have not -&& overloads, but will at least allow user-defined containers to return `std::move_iterator`s in that case. 

On Friday, 6 October 2017 22:55:20 UTC+2, Aarón Bueno Villares wrote:

Nicol Bolas

unread,
Oct 6, 2017, 9:40:14 PM10/6/17
to ISO C++ Standard - Discussion


On Friday, October 6, 2017 at 7:34:40 PM UTC-4, Aarón Bueno Villares wrote:
Sorry I write too fast and used auto accidentally.

Change:

    for (auto&& obj : f())


by

    for(a_big_struct_t&& obj : f())


and it doesn't compile because the returned list is transparently a lvalue.

My question is if that makes sense to be allowed.

No, it shouldn't, because `std::move` or something similar is never spelled out. So having an rvalue reference doesn't make sense.

Now, one might suggest using `move_iterator` or a similar tool, but unfortunately, that causes a temporary problem. If you try to do `move_range(f())`, the lifetime of the container will expire before the loop starts.

Of course, this is why P0614 was adopted for C++20. So that we could do this:

for(auto && rng = f(); a_big_struct &&obj : move_range(rng))


gmis...@gmail.com

unread,
Oct 9, 2017, 4:44:03 AM10/9/17
to ISO C++ Standard - Discussion
Do you happen to know If the increment part of that proposal was also adopted and if not, why not?
Thanks

barry....@gmail.com

unread,
Oct 9, 2017, 9:41:23 AM10/9/17
to ISO C++ Standard - Discussion, gmis...@gmail.com


On Monday, October 9, 2017 at 3:44:03 AM UTC-5, gmis...@gmail.com wrote:
Do you happen to know If the increment part of that proposal was also adopted and if not, why not?
Thanks


It wasn't part of the proposal. It's indicated as a "possible future extension" 

Richard Hodges

unread,
Oct 9, 2017, 10:30:46 AM10/9/17
to std-dis...@isocpp.org
We can currently make a wrapper that keeps the underlying container alive with no overhead, if that helps.

#include <vector>
#include <string>
#include <iostream>
#include <iterator>

template<class Container>
struct moveable_container : Container
{
using underlying_container = Container;
using underlying_container::underlying_container;

explicit moveable_container(underlying_container&& other)
: underlying_container(std::move(other))
{}

auto begin() { return std::make_move_iterator(underlying_container::begin()); }
auto begin() const { return underlying_container::begin(); }

auto end() { return std::make_move_iterator(underlying_container::end()); }
auto end() const { return underlying_container::end(); }
};

template<class Cont> moveable_container(Cont &&) -> moveable_container<std::decay_t<Cont>>;

std::vector<std::string> make_strings()
{
std::vector<std::string> result;
result.emplace_back("Hello,");
result.emplace_back("World!");
return result;
}

int main()
{
for (std::string&& x : moveable_container(make_strings()))
{
auto s = std::move(x);
std::cout << s << std::endl;
}
}

--

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

gmis...@gmail.com

unread,
Oct 9, 2017, 5:01:52 PM10/9/17
to ISO C++ Standard - Discussion, gmis...@gmail.com, barry....@gmail.com
I found it a little odd that it wasn't part of the proposal.
But I found it more surprising the committee adopted the proposal without requesting the increment part too.
So that made me wonder if there was something contentious about the increment part because it seems a glaring omission to me.

Nicol Bolas

unread,
Oct 9, 2017, 5:21:01 PM10/9/17
to ISO C++ Standard - Discussion, gmis...@gmail.com, barry....@gmail.com
On Monday, October 9, 2017 at 5:01:52 PM UTC-4, gmis...@gmail.com wrote:
I found it a little odd that it wasn't part of the proposal.
But I found it more surprising the committee adopted the proposal without requesting the increment part too.
So that made me wonder if there was something contentious about the increment part because it seems a glaring omission to me.

The "increment part" wasn't really a proposal. It was just an idea, a one-sentence blurb. It never even tries to explain what `incr` would actually do.

After all, incrementing an iterator requires, well, having an iterator to increment. And by the nature of range-based for, the iterator variable is hidden. So, how do you increment it? Do you provide a function that is given the iterator?

And personally, I see no real reason for such a thing. With the initializer, you now have the ability to do all of the prvalue chaining you need, so you can incorporate special incrementing logic into the range itself via adaptors. Why bother with having extra syntax for something that works adequately as is?

T. C.

unread,
Oct 9, 2017, 8:16:38 PM10/9/17
to ISO C++ Standard - Discussion


On Monday, October 9, 2017 at 10:30:46 AM UTC-4, Richard Hodges wrote:
template<class Cont> moveable_container(Cont &&) -> moveable_container<std::decay_t<Cont>>;

Off-topic note: This is better written as

template<class Cont> moveable_container(Cont) -> moveable_container<Cont>;

Instead of taking a forwarding reference and manually decaying the type, just let the compiler do the decay for you.
 

gmis...@gmail.com

unread,
Oct 9, 2017, 10:59:25 PM10/9/17
to ISO C++ Standard - Discussion, gmis...@gmail.com, barry....@gmail.com

I was anticipating the compiler incrementing the iterator then calling the code that was part of the increment step and giving the increment step code no access to the iterator.

So it's a limitation not being able to see the iterator, but it allows the user to have other things that they may want to keep in step with the iterator (like increment their own integer or something) and have that code fire on 'continue' etc. Where as that is not possible with the current proposal as continue bypasses any such logic that is at the end of the loop.

One could try to have a function in the increment step that receives the iterator, but that seems unusual compared the main loop constructs and what I'm suggesting is simpler and probably meets the typical use cases I can think of, which is that someone doesn't need the iterator but does need some logic that attempts to sync their own values with the iterator some how.

To me it seems to make move this for loop construct closer to the original one which aids understanding and provides additional benefit too without taking it so far as to diverge into something that looks/works quite differently.

Do you find anything objectionable about that idea?

Nicol Bolas

unread,
Oct 9, 2017, 11:41:12 PM10/9/17
to ISO C++ Standard - Discussion, gmis...@gmail.com, barry....@gmail.com
On Monday, October 9, 2017 at 10:59:25 PM UTC-4, gmis...@gmail.com wrote:

I was anticipating the compiler incrementing the iterator then calling the code that was part of the increment step and giving the increment step code no access to the iterator.

So it's a limitation not being able to see the iterator, but it allows the user to have other things that they may want to keep in step with the iterator (like increment their own integer or something) and have that code fire on 'continue' etc. Where as that is not possible with the current proposal as continue bypasses any such logic that is at the end of the loop.

One could try to have a function in the increment step that receives the iterator, but that seems unusual compared the main loop constructs and what I'm suggesting is simpler and probably meets the typical use cases I can think of, which is that someone doesn't need the iterator but does need some logic that attempts to sync their own values with the iterator some how.

To me it seems to make move this for loop construct closer to the original one which aids understanding and provides additional benefit too without taking it so far as to diverge into something that looks/works quite differently.

Do you find anything objectionable about that idea?

Yes. A regular `for` loop needs an iteration expression to do iteration. Range-based `for` has iteration built-in; why would you need a user-defined expression?

Range-based for loops are supposed to be pretty simple. You have a range of values, and you're iterating over them. If you have some code that will be executed on every iteration, even in the event of a `continue`, then that makes the looping logic a lot less simple.

If you need more complexity, then maybe range-based for is the wrong tool. I don't like the idea of trying to make range for able to do anything.

We only gave them an initializer to deal with a pernicious problem with temporary lifetime extension through function calls. If we had a good language feature for extending the lifetime of an expression, or doing so automatically and correctly, we wouldn't have needed even the initializer.


gmis...@gmail.com

unread,
Oct 10, 2017, 1:20:50 AM10/10/17
to ISO C++ Standard - Discussion, gmis...@gmail.com, barry....@gmail.com

Yes. A regular `for` loop needs an iteration expression to do iteration. Range-based `for` has iteration built-in; why would you need a user-defined expression?

Because it might not be that it's just iteration you want to do and that you want to do something after each iteration. Such as increment some other counter that you want to keep in step. You might not need nor want to capture all that in a lambda or something just so that you can for each it and if you want to do 'continue' type operations to skip your loop part, you can't manage that easily from a lambda anyway.
 

Range-based for loops are supposed to be pretty simple. You have a range of values, and you're iterating over them. If you have some code that will be executed on every iteration, even in the event of a `continue`, then that makes the looping logic a lot less simple.

If you need more complexity, then maybe range-based for is the wrong tool. I don't like the idea of trying to make range for able to do anything.

Maybe you're right sometimes there. But maybe you're not right all of the time. Isn't C++ a language where the user gets to decide?
I don't see (yet) any killer argument to not allow the facility. I've given a few examples above why it might be useful to allow it.

Nicol Bolas

unread,
Oct 10, 2017, 9:49:35 AM10/10/17
to ISO C++ Standard - Discussion, gmis...@gmail.com, barry....@gmail.com


On Tuesday, October 10, 2017 at 1:20:50 AM UTC-4, gmis...@gmail.com wrote:

Yes. A regular `for` loop needs an iteration expression to do iteration. Range-based `for` has iteration built-in; why would you need a user-defined expression?

Because it might not be that it's just iteration you want to do and that you want to do something after each iteration.

... then why is it called an "iteration" expression?
 
Such as increment some other counter that you want to keep in step. You might not need nor want to capture all that in a lambda or something just so that you can for each it and if you want to do 'continue' type operations to skip your loop part, you can't manage that easily from a lambda anyway.


Range-based for loops are supposed to be pretty simple. You have a range of values, and you're iterating over them. If you have some code that will be executed on every iteration, even in the event of a `continue`, then that makes the looping logic a lot less simple.

If you need more complexity, then maybe range-based for is the wrong tool. I don't like the idea of trying to make range for able to do anything.

Maybe you're right sometimes there. But maybe you're not right all of the time. Isn't C++ a language where the user gets to decide?
I don't see (yet) any killer argument to not allow the facility. I've given a few examples above why it might be useful to allow it.

You've got that backwards. You have to provide a "killer argument" why we should "allow the facility". That something could be useful is not enough. It has to be useful enough to be worth the effort of adding more syntax to this feature.

`if/switch` initializers solved a problem that has been present for a long time, one that we have innumerable examples of the need for. Range-`for` initializers solve a long-standing problem with range-`for`: the lifetime of temporaries in the range expression.

In short, those features solve problems that lots of people have. Does this iteration expression do the same?





tko...@google.com

unread,
Oct 10, 2017, 5:19:45 PM10/10/17
to ISO C++ Standard - Discussion, gmis...@gmail.com, barry....@gmail.com
On Tuesday, 10 October 2017 04:41:12 UTC+1, Nicol Bolas wrote:
We only gave them an initializer to deal with a pernicious problem with temporary lifetime extension through function calls.

That wasn't exactly the only reason, nor was it a particularly singular motivation. It's a nice use case for the initializer to hold a container, but the motivation for the proposal was more general, namely that it is common for loops in general to require some additional state that's only required for the duration of the loop, and that it's useful to have a space for that. There are other examples in the paper, and I expect there to be many uses of this feature that are not specifically about resolving lifetime issues with containers.

I don't see P0614 specifically as an attempt to forestall language evolution with regards to object lifetime, though it may come up in those discussions. Personally, I feel that the new initializers in iteration and selection statements give me enough tools to deal with lifetimes, but others may very well feel differently and propose farther-reaching changes.
Reply all
Reply to author
Forward
0 new messages