56

I just discovered that at one point, the C++11 draft had std::begin/std::end overloads for std::pair that allowed treating a pair of iterators as a range suitable for use in a range-based for loop (N3126, section 20.3.5.5), but this has since been removed.

Does anyone know why it was removed?

I find the removal very unfortunate, because it seems there is no other way to treat a pair of iterators as a range. Indeed:

  • The lookup rules for begin/end in a range-based for loop say that begin/end are looked for in 1) as member functions of the range object 2) as free functions in "associated namespaces"
  • std::pair does not have begin/end member functions
  • The only associated namespace for std::pair<T, U> in general is namespace std
  • We are not allowed to overload std::begin/std::end for std::pair ourselves
  • We cannot specialize std::begin/std::end for std::pair (because the specialization would have to be partial and that's not allowed for functions)

Is there some other way that I am missing?

Soo Wei Tan
  • 3,262
  • 2
  • 34
  • 36
HighCommander4
  • 50,428
  • 24
  • 122
  • 194
  • Isn't begin(), end() for a std::pair the same thing as .first and .second? – Himadri Choudhury May 29 '11 at 12:01
  • @Himadri: Yes, but to use it in a range-based for loop, we need begin() and end(). – HighCommander4 May 29 '11 at 20:45
  • 2
    Probably, because of semantics (a pair isn't necessarilty a range) and to avoid ambiguities, such as e.g. reported here: http://stackoverflow.com/questions/4155450/adl-with-typedefs-from-another-namespace/4155508# – sehe Dec 19 '11 at 00:40

3 Answers3

44

I think the 2009 paper "Pairs do not make good ranges" by Alisdair Meredith is at least part of the answer. Basically, many algorithms return pairs of iterators that are actually not guaranteed to be valid ranges. It seems they removed the support for pair<iterator,iterator> from the for-range loop for this reason. However, the proposed solution has not been fully adopted.

If you know for certain that some pair of iterators really represents a valid range then you could wrap them into a custom type which offers begin()/end() member functions:

template<class Iter>
struct iter_pair_range : std::pair<Iter,Iter> {
    iter_pair_range(std::pair<Iter,Iter> const& x)
    : std::pair<Iter,Iter>(x)
    {}
    Iter begin() const {return this->first;}
    Iter end()   const {return this->second;}
};

template<class Iter>
inline iter_pair_range<Iter> as_range(std::pair<Iter,Iter> const& x)
{ return iter_pair_range<Iter>(x); }

int main() {
    multimap<int,int> mm;
    ...
    for (auto& p : as_range(mm.equal_range(42))) {
       ...
    }
}

(untested)

I agree this is a bit of a wart. Functions which return valid ranges (like equal_range) should say so using an appropriate return type. It's a bit embarrasing that we have to manually confirm this via something like as_range above.

sellibitze
  • 27,611
  • 3
  • 75
  • 95
  • 3
    boost::range does exactly that. using boost;:range instead of something handwritten might be preferable. [1]: http://www.boost.org/doc/libs/1_53_0/libs/range/doc/html/range/reference/utilities/iterator_range.html – Alexander Oh Jul 29 '13 at 09:36
  • 1
    Meredith identified only 5 library functions affected by this problem. Wouldn't it be easier for them to simply have those functions guarantee valid ranges, rather than extend the type system? I'm not really sure I see how this new type really solves anything anyway; you can still create an instance of one with iterators that don't make a valid range. – Ben Collins Aug 07 '13 at 14:41
  • 1
    @BenCollins: I think the main issue is that there's lots of existing application code that uses pair for other purposes (this would probably be unusual given the ephemeral nature of iterators, but you could imagine a map of iterators to iterators for example). Defining a separate type for an iterator range makes its purpose clear and guarantees there won't be any unexpected side effects from existing application code. – Miral Oct 04 '13 at 00:04
  • `boost::make_iterator_range` does the same as thing for you. I understand that this code is for exposition only. – Maxim Egorushkin Aug 20 '14 at 08:37
9

You can use boost::make_iterator_range. It constructs an iterator_range with begin() and end() methods. boost::make_iterator_range can accept std::pair of iterators.

mgsergio
  • 91
  • 1
  • 1
6

expanding on the above answer using c++11 optimisations:

#include <utility>

template<class Iter>
struct range_t : public std::pair<Iter, Iter> {
    using pair_t = std::pair<Iter, Iter>;
    range_t(pair_t&& src)
    : std::pair<Iter, Iter>(std::forward<pair_t>(src))
    {}

    using std::pair<Iter, Iter>::first;
    using std::pair<Iter, Iter>::second;

    Iter begin() const { return first; }
    Iter end() const { return second; }
};

template<class Iter>
range_t<Iter> range(std::pair<Iter, Iter> p) {
    return range_t<Iter>(std::move(p));
}

template<class Iter>
range_t<Iter> range(Iter i1, Iter i2) {
    return range_t<Iter>(std::make_pair(std::move(i1), std::move(i2)));
}


// TEST: 

#include <iostream>
#include <set>
using namespace std;

int main() {

    multiset<int> mySet { 6,4,5,5,5,3,3,67,8,89,7,5,45,4,3 };

    cout << "similar elements: ";
    for (const auto&i : range(mySet.lower_bound(5), mySet.upper_bound(10))) {
        cout << i << ",";
    }
    cout << "\n";

    int count = 0, sum = 0;
    for (const auto& i: range(mySet.equal_range(5)))
    {
        ++count;
        sum += i;
    }
    cout << "5 appears " << count << " times\n"
    << "the sum is " << sum << "\n";

return 0;
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 3
    That works really well, minor quibble - posix reserves the postfix `_t`. See: [What are the rules about using an underscore in a C++ identifier?](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier). – Eoin Jun 24 '14 at 12:11
  • "Names that end with '_t' are reserved for additional type names." range_t<> is a type. So all is good :-) – Richard Hodges Jun 24 '14 at 18:09
  • 2
    From the linked answer - "Some additional classes of identifier names are reserved for future extensions to the C language or the POSIX.1 environment. While using these names for your own purposes right now might not cause a problem, they do raise the possibility of conflict with future versions of the C or POSIX standards, so you should avoid these names." So, just because you are using it for a type doesn't prevent a possible future conflict. – Eoin Jun 25 '14 at 00:05
  • With respect I think you'll find that any future _t type names will be in the std:: namespace. The suffix _t is reserved for types - as opposed to instances of types. So this is wrong: `int my_int_t;` and this is right: `struct my_tag_t {};` – Richard Hodges Jun 26 '14 at 09:57
  • 3
    Honestly no, POSIX lays claim to all identifiers suffixed with `_t`, see [GNU - Reserved Names](http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html). So POSIX considers itself free to, now or at any point in the future, declare a new type with that suffix in the global namespace. POSIX isn't C++ so a new type won't be nested in `std` it will be global. If that type conflicts with a type in an existing codebase, then that codebase is considered to be (and have always been) in the wrong for not playing by the rules. – Eoin Jun 26 '14 at 14:05
  • 3
    hmm. in that case posix is at odds with a large part of the boost library and some fairly influential c++ developers. I guess if we want to be super-safe we can argue that a _t on a type is permissible in any namespace other than the global namespace - since that's the only one that can possibly be polluted by c (and therefore posix) code. – Richard Hodges Jun 26 '14 at 16:15
  • 2
    This "minor quibble" has gone a bit further than intended ;-). I agree completely that a lot of developers have adopted the convention. Personally I avoid the suffix, for example in adopting your solution I used `struct range`, and `make_range` for the free functions. But that is a case of erring on the side of being extra cautious. – Eoin Jun 26 '14 at 20:18
  • 2
    The discussion was useful - I learned something :-) I quite like the _t suffix as it allows me to assert that the name is a type in an unobtrusive way. I quite like using `lower_case_names` in c and c++ as I find them easier and more natural to read than `camelCaps`. I also often name functors like this: `struct do_something_f;` to assert that it's a function object. – Richard Hodges Jun 27 '14 at 12:33
  • 2
    The ctor from `pair_t` is taking an rvalue reference and thus only binds to rvalues. It is not a universal/forwarding reference, because the argument it not deduced. The `std::forward` call is passed a non-reference type, so unconditionally moves. That's ok, but you probably want to accept lvalues, too. – Marc Mutz - mmutz Feb 08 '16 at 19:44
  • Yeah that. For a move-worthy argument type the ctor should take a value not a ref. Although I question the need to do _any_ move stuff with iterators; it's likely not worth it. Do any iterators have meaningful move semantics? – Lightness Races in Orbit May 17 '19 at 00:19