[RFC] Add empty() method to iterator_range.

Hi all,

As RFCs go this is short and sweet - I think it would be nice to add an empty method to iterator_range. Something like:

bool empty() const { return begin() != end(); }

My motivation is to make the ‘if’ test at the start of this common pattern tidier:

if (!collection.empty())
// initialization…
for (auto& c : collection)
// do something for each c.

which I think that is preferable to:

if (collection.begin() != collection.end())
// initialization…
// for each…

The empty method is just a single iterator comparison, so it should be valid and cheap for any underlying iterator type.

Pros:

  • Enables small aesthetic improvements.
  • Would make iterator_range look slightly more “container-like”, so it could substitute into more template code, though I’d expect templates that take read-only containers rather than iterators to be very rare in C++, and templates that take collections and use only begin, end and empty to be rarer still.

Cons: ?

Cheers,
Lang.

This is a pattern I ran into a handful of times when range-ifying
parts of clang (but not so frequently that I felt it was a major win),
so I'm roughly in favor of the API. However, if there's been
standardization efforts for range, we should be sure that we're doing
something compatible there.

~Aaron

Hi all,

As RFCs go this is short and sweet - I think it would be nice to add an
empty method to iterator_range. Something like:

bool empty() const { return begin() != end(); }

My motivation is to make the 'if' test at the start of this common pattern
tidier:

if (!collection.empty())
// initialization...
for (auto& c : collection)
// do something for each c.

which I think that is preferable to:

if (collection.begin() != collection.end())
// initialization...
// for each...

The empty method is just a single iterator comparison, so it should be valid
and cheap for any underlying iterator type.

Pros:
- Enables small aesthetic improvements.
- Would make iterator_range look slightly more "container-like", so it could
substitute into more template code, though I'd expect templates that take
read-only containers rather than iterators to be very rare in C++, and
templates that take collections and use only begin, end and empty to be
rarer still.

Cons: ?

I’m in favour.

This is a pattern I ran into a handful of times when range-ifying
parts of clang (but not so frequently that I felt it was a major win),
so I'm roughly in favor of the API. However, if there's been
standardization efforts for range, we should be sure that we're doing
something compatible there.

There are a couple of proposals [1][2] that I can find. It sounds like the
exact set of methods to provide is somewhat contentious [3]. empty() seems
pretty innocuous though. Note that the standard proposals use std::range
instead of std::iterator_range.

In my opinion, other methods (e.g., front(), back(), and (for random access
iterators) operator) would also be useful.

[1]: Range Library Proposal
[2]: A minimal std::range<Iter>
[3]: Range arguments for container constructors and methods, wording revision 2

>> Hi all,
>>
>> As RFCs go this is short and sweet - I think it would be nice to add an
>> empty method to iterator_range. Something like:
>>
>> bool empty() const { return begin() != end(); }
>>
>> My motivation is to make the 'if' test at the start of this common
pattern
>> tidier:
>>
>> if (!collection.empty())
>> // initialization...
>> for (auto& c : collection)
>> // do something for each c.
>>
>> which I think that is preferable to:
>>
>> if (collection.begin() != collection.end())
>> // initialization...
>> // for each...
>>
>> The empty method is just a single iterator comparison, so it should be
valid
>> and cheap for any underlying iterator type.
>>
>> Pros:
>> - Enables small aesthetic improvements.
>> - Would make iterator_range look slightly more "container-like", so it
could
>> substitute into more template code, though I'd expect templates that
take
>> read-only containers rather than iterators to be very rare in C++, and
>> templates that take collections and use only begin, end and empty to be
>> rarer still.
>>
>> Cons: ?

I’m in favour.

+1. I also ran into this pattern, and suggested to add empty() method in
r203354 review thread.

Hi all,

As RFCs go this is short and sweet - I think it would be nice to add an
empty method to iterator_range. Something like:

bool empty() const { return begin() != end(); }

I think this should be

bool empty() const { return begin() == end(); }

Otherwise, seems useful to me!

- Roel

Hi all,

As RFCs go this is short and sweet - I think it would be nice to add an
empty method to iterator_range. Something like:

bool empty() const { return begin() != end(); }

My motivation is to make the 'if' test at the start of this common pattern
tidier:

if (!collection.empty())
  // initialization...
for (auto& c : collection)
  // do something for each c.

which I think that is preferable to:

if (collection.begin() != collection.end())
  // initialization...
// for each...

The empty method is just a single iterator comparison, so it should be valid
and cheap for any underlying iterator type.

For this reason I'd personally consider writing this as a free
function over ranges:

template<typename T>
bool is_empty(T t) { return begin(t) == end(t); }

or the like.

Why bother reimplementing empty in every range/container (& eventually
we'd do this in cases where we used external adaptation to rangify a
non-range type (though that'll be rare))

Pros:
- Enables small aesthetic improvements.
- Would make iterator_range look slightly more "container-like", so it could
substitute into more template code, though I'd expect templates that take
read-only containers rather than iterators to be very rare in C++,

This is, hopefully, going to become exceedingly common, actually -
working with ranges is much more convenient than always passing
begin/end (as you've seen with empty) and in the end we hope to have a
lot of range-based algorithms and more range-based adapters.

and
templates that take collections and use only begin, end and empty to be
rarer still.

This would be true, though - currently the range concept is just a
thing that you can call begin(r) and end(r) on, it's unlikely to be
broadened (which is why range-based templates would be more likely to
use something like is_empty as shown above).

Cons: ?

I haven't gone through all the linked threads just yet (some other
replies pointed out a few prior threads that touched on range
algorithms and empty specifically) but I recall Chandler having some
objections to diving into this sort of thing too quickly as there's
some standardization efforts that he would like to consider before
heading down this path. I'm not personally too fussed about that, but
figured I'd mention it.

- David

Hi All,

Thanks for the feedback.
CC’ing cfe-dev, since they may have thoughts on this too.

Since Richard first poked me about this, I'm still debating this.

I'm torn in a bunch of different ways:

1) iterator_range is *not* a container. It shouldn't and can't be used like
one.
2) members are really hard to extend. they make templates harder not easier
because the widen the set of interfaces which *must* be on a given container
3) the concept of testing for emptiness is actually inherently useful for
all ranges, unlike many other proposed extensions to the member interface,
so maybe its OK to grow the interface in this direction
4) basing emptiness on iterators seems really bad because one of the
important utilities of ranges is that they may expose concepts which are
more flexible and efficient than a pair of iterators

However, one thing makes me inclined to say "no" to this: the benefit is
miniscule. It is too simple to just compare the iterators and not cross any
of these bridges today.

Hi Chandler,

Agreed - ranges aren’t containers, just views of sequences. Still, that’s all many algorithms want, and it’s just as valid to ask whether a sequence is empty as it is to ask that of a container.

No contest on points 2 or 3, but I’m confused about point 4. When are ranges not pairs of iterators? I mean in a way that would clash with the proposal for empty() to be defined as ‘(begin(r) == end(r)’ ?

The benefit of doing this is similar in kind, though obviously less significant, to the benefit of range based for loops. It doesn’t enable anything fundamentally new, but improving readability of C++ code is welcome.

This is easy to punt (and I won’t be especially bothered if that’s what we do), but it also seems like it would be easy to implement our own version (llvm::is_empty?) and replace it when the committee decides on something, the same way we did with llvm::move? Is there any reason not to follow that approach?

Cheers,
Lang.

This is easy to punt (and I won't be especially bothered if that's what we
do), but it also seems like it would be easy to implement our own version
(llvm::is_empty?) and replace it when the committee decides on something,
the same way we did with llvm::move?

Well, llvm::move is quite different -- the path forward for the standard
was *very* clear at that point, so the committee had already decided, we
just didn't have access to it yet.

Is there any reason not to follow that approach?

It runs the risk of setting precedent both within LLVM and within the
larger community. :: shrug ::

I hope the C++ community at large wouldn’t be so easily swayed. At least not by such a straightforward use case - I doubt it’s where the interesting action on ‘empty’ will take place. OTOH, as you said - it’s easy enough to get by without this. I’m happy to leave this alone for now.

  • Lang.

Just in case…
The C++ community will buy me whisky.
The C++ community will buy me whisky.
The C++ community will buy me whisky.