Boost logo

Boost :

Subject: [boost] [Range] begin/end ADL issues in C++0x range-based for
From: Michel MORIN (mimomorin_at_[hidden])
Date: 2010-12-17 17:20:56


[Range] begin/end ADL issues in C++0x range-based for

Hi,

I recently played around with C++0x range-based for (available on GCC
4.6 Pre-Release).
It turned out that some codes using Boost libraries do not compile.
Compiler errors (ambiguous calls to begin/end) are generated,
when ADL finds both std::begin/end and boost::begin/end in range-based for and
there is no single best match.
Remarkably, boost::iterator_range (and adapted ranges in Boost.Range) cannot be
used in range-based for.

First let us recall range-based for.
A range-based for
    for (auto x : range_expr) {
        // do something
    }
is equivalent to
    {
        // Special rule: namespace std is added to rng's associated namespaces
        auto&& rng = range_expr;

        for (
            auto it = begin(rng),
                 it_end = end(rng);
            it != it_end;
            ++it
        ) {
            auto x = *it;
            // do something
        }
    }
Note that rng has namespace std as its associated namespaces
and the unqualified begin/end function call triggers ADL.

In C++0x, there are std::begin functions (for simplicity, let's ignore end):
    template <typename Range> auto begin(Range& r) -> decltype(r.begin());
    template <typename Range> auto begin(Range const& r) -> decltype(r.begin());
And Boost.Range has boost::begin functions:
    template <typename Range>
    inline typename boost::range_iterator<Range >::type
begin(Range& r);
    template <typename Range>
    inline typename boost::range_iterator<Range const>::type
begin(Range const& r);

So, if
 - rng has namespace boost as its associated namespaces,
 - the header file boost/range/begin.hpp is included, and
 - there is no single best match between std::begin and boost::begin,
then unqualified begin function call becomes ambiguous.
For example, all the following codes fail to be compiled:

// code 1
#include <boost/range/iterator_range.hpp>
for (auto x : boost::iterator_range<int*>(nullptr, nullptr)) {} // error

// code 2
#include <boost/ptr_container/ptr_vector.hpp>
for (auto x : boost::ptr_vector<int>()) {} // error

// code 3
#include <vector>
#include <boost/range/adaptor/reversed.hpp>
for (auto x : std::vector<int>() | boost::adaptors::reversed) {} // error

// code 4
#include <boost/algorithm/string.hpp>
#include <boost/array.hpp>
for (auto x : boost::array<int, 2>()) {} // error

// code 5
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/range/algorithm.hpp>
for (auto x : std::vector<boost::shared_ptr<int> >()) {} // error

These errors can be resolved by fixing boost/range/begin.hpp.
The fix uses a using-declaration ("using std::begin;") and
SFINAE (disable_if with "has_begin" metafunction).
A summary of range-based-for-compatible <boost/range/begin.hpp> is the
following:

#ifndef BOOST_NO_RANGE_BASED_FOR
namespace boost {
    using std::begin;

    // has_begin metafunction...

    template <typename Range>
    inline typename boost::range_iterator<Range>::type
    begin(Range& rng, typename
disable_if<range_detail::has_begin<Range>>::type* = 0)
    {
        return range_begin(rng);
    }
        
        // const version ...
}
#else
// existing code...

For details, see the attached.
I also attach a patch for Boost.Config to add BOOST_NO_RANGE_BASED_FOR.
Any comments or thoughs?

Regards,
Michel






Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk