|
Boost : |
Subject: [boost] [Foreach] Supporting range adaptors for temporary containers
From: Michel MORIN (mimomorin_at_[hidden])
Date: 2011-04-21 16:07:49
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
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk