[Foreach] Supporting range adaptors for temporary containers

Sometimes it is convenient to apply range adaptors to a temporary container and iterate over it: // `using namespace boost::adaptors;` is assumed BOOST_FOREACH(auto x, std::string("Hello, world!") | reversed) {...} However, the lifetime of the temporary container ends before the loop body begins. This problem also exists in C++0x range-based for. To solve this, I'd like to propose an extension of BOOST_FOREACH macro: BOOST_ADAPTED_FOREACH(VARIABLE, RANGE, ADAPTORS) { // do something } which is conceptually equivalent to { auto const& rng = RANGE; // if RANGE is an lvalue, use `auto& rng = RANGE` BOOST_FOREACH(VARIABLE, rng | ADAPTORS) { // do something } } A temporary range, RANGE, is bound to a const reference and its lifetime is extended. (To do this, compiler support of rvalue references and `auto`/ `decltype` is needed. In C++03, a temporary range needs to be copied.) Then we can do the following without worry about the lifetime problem. BOOST_ADAPTED_FOREACH( char ch , std::string("Hello, world!") , reversed | replaced('e', 'a')) { std::cout << ch; } To bind a temporary range, we can also use (non-const) rvalue reference. But I choose to use const lvalue reference. The rationale is * In the pipe operator, a temporary range is captured as a const reference. So if we use rvalue reference binding, the mutability of the resulting range (`auto&& rng = RANGE; rng | ADAPTORS`) and `RANGE | ADAPTORS` would be different. For example, `std::list<int>(3) | reversed` is a const range, but `auto&& rng = std::list<int>(3); rng | reversed` is a mutable range. To be consistent with `RANGE | ADAPTORS`, using const lvalue reference (`auto const& rng = RANGE; rng | ADAPTORS`) is better. * Mutable iteration over temporary ranges makes little sense. Note that, BOOST_ADAPTED_FOREACH works fine if an input range is a temporary containers, but it does not work properly if an input is a range-proxy (such as `iterator_range`) of a temporary container. In this case, only the lifetime of range-proxies is extended and dangling reference to the temporary range occurs: BOOST_ADAPTED_FOREACH( int i , boost::equal_range(std::multiset<int>(...), 1) , reversed) { // Oops, dangling reference! // Input is an `iterator_range` of the temporary multiset<int>(...) } BOOST_ADAPTED_FOREACH( int i , boost::assign::list_of(1)(2)(3)(4)(5) , reversed) { // Oops, dangling reference! // Input is a reference to the temporary `list_of(1)` } Header file and sample code attached. (Though I omitted the C-string support for simplicity, it is easy to add the support.) If there are some interests about this proposal, I will create a trac ticket. Comments? Regards, Michel

On Thu, Apr 21, 2011 at 1:07 PM, Michel MORIN <mimomorin@gmail.com> wrote:
Sometimes it is convenient to apply range adaptors to a temporary container and iterate over it:
// `using namespace boost::adaptors;` is assumed BOOST_FOREACH(auto x, std::string("Hello, world!") | reversed) {...}
However, the lifetime of the temporary container ends before the loop body begins.
[...] I believe BOOST_FOREACH correctly accounts for rvalue range expressions. See second-to-last example from http://www.boost.org/doc/libs/1_46_1/doc/html/foreach.html Does this address your concern? - Jeff

Jeffrey Lee Hellrung, Jr. wrote:
On Thu, Apr 21, 2011 at 1:07 PM, Michel MORIN <mimomorin@gmail.com> wrote:
Sometimes it is convenient to apply range adaptors to a temporary container and iterate over it:
// `using namespace boost::adaptors;` is assumed BOOST_FOREACH(auto x, std::string("Hello, world!") | reversed) {...}
However, the lifetime of the temporary container ends before the loop body begins.
I believe BOOST_FOREACH correctly accounts for rvalue range expressions. See second-to-last example from
http://www.boost.org/doc/libs/1_46_1/doc/html/foreach.html
Does this address your concern?
No. extern std::vector<float> get_vector_float(); BOOST_FOREACH( float f, get_vector_float() ) { ... } This code is OK. A temporary range returned by `get_vector_float()` is copied. In C++0x range-based for, the temporary range is bound to an rvalue reference and its lifetime is extended. extern std::vector<float> get_vector_float(); BOOST_FOREACH( float f, get_vector_float() | reversed ) { ... } But this code results in a dangling reference. ONLY a temporary iterator_range returned by `get_vector_float() | reversed` is copied (or bound to an rvalue reference in C++0x range-based for). The lifetime of `get_vector_float()` ends before the loop body begins! Regards, Michel

On Thu, Apr 21, 2011 at 3:43 PM, Michel MORIN <mimomorin@gmail.com> wrote:
On Thu, Apr 21, 2011 at 1:07 PM, Michel MORIN <mimomorin@gmail.com> wrote:
Sometimes it is convenient to apply range adaptors to a temporary container and iterate over it:
// `using namespace boost::adaptors;` is assumed BOOST_FOREACH(auto x, std::string("Hello, world!") | reversed) {...}
However, the lifetime of the temporary container ends before the loop
Jeffrey Lee Hellrung, Jr. wrote: body
begins.
I believe BOOST_FOREACH correctly accounts for rvalue range expressions. See second-to-last example from
http://www.boost.org/doc/libs/1_46_1/doc/html/foreach.html
Does this address your concern?
No.
extern std::vector<float> get_vector_float(); BOOST_FOREACH( float f, get_vector_float() ) { ... }
This code is OK. A temporary range returned by `get_vector_float()` is copied. In C++0x range-based for, the temporary range is bound to an rvalue reference and its lifetime is extended.
extern std::vector<float> get_vector_float(); BOOST_FOREACH( float f, get_vector_float() | reversed ) { ... }
But this code results in a dangling reference. ONLY a temporary iterator_range returned by `get_vector_float() | reversed` is copied (or bound to an rvalue reference in C++0x range-based for). The lifetime of `get_vector_float()` ends before the loop body begins!
Ah, right. I didn't read your original msg closely enough, apparently. Sounds like a legitimate use case... - Jeff

Message du 22/04/11 00:44 De : "Michel MORIN" A : boost@lists.boost.org Copie à : Objet : Re: [boost] [Foreach] Supporting range adaptors for temporary containers
Jeffrey Lee Hellrung, Jr. wrote:
On Thu, Apr 21, 2011 at 1:07 PM, Michel MORIN wrote:
Sometimes it is convenient to apply range adaptors to a temporary container and iterate over it:
// `using namespace boost::adaptors;` is assumed BOOST_FOREACH(auto x, std::string("Hello, world!") | reversed) {...}
However, the lifetime of the temporary container ends before the loop body begins.
I believe BOOST_FOREACH correctly accounts for rvalue range expressions. See second-to-last example from
http://www.boost.org/doc/libs/1_46_1/doc/html/foreach.html
Does this address your concern?
No.
extern std::vector get_vector_float(); BOOST_FOREACH( float f, get_vector_float() ) { ... }
This code is OK. A temporary range returned by `get_vector_float()` is copied. In C++0x range-based for, the temporary range is bound to an rvalue reference and its lifetime is extended.
extern std::vector get_vector_float(); BOOST_FOREACH( float f, get_vector_float() | reversed ) { ... }
But this code results in a dangling reference. ONLY a temporary iterator_range returned by `get_vector_float() | reversed` is copied (or bound to an rvalue reference in C++0x range-based for). The lifetime of `get_vector_float()` ends before the loop body begins!
Sorry if my question is innocent. Would the functional form of the adaptor work? extern std::vector get_vector_float(); BOOST_FOREACH( float f, reverse(get_vector_float()) ) { ... } Best, Vicente

Vicente BOTET wrote:
extern std::vector get_vector_float(); BOOST_FOREACH( float f, get_vector_float() | reversed ) { ... }
But this code results in a dangling reference. ONLY a temporary iterator_range returned by `get_vector_float() | reversed` is copied (or bound to an rvalue reference in C++0x range-based for). The lifetime of `get_vector_float()` ends before the loop body begins!
Sorry if my question is innocent. Would the functional form of the adaptor work?
extern std::vector get_vector_float(); BOOST_FOREACH( float f, reverse(get_vector_float()) ) { ... }
No. `boost::adaptors::reverse(rng)` is a synonym of `rng | boost::adaptors::reversed` and so this does not work properly. Only a temporary iterator_range is copied and the lifetime of a temporary container ends before the loop body begins! Regards, Michel
participants (3)
-
Jeffrey Lee Hellrung, Jr.
-
Michel MORIN
-
Vicente BOTET