Boost logo

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