10

in c++ 11 if we have a set<int> S; we could say:

for (auto i: S)
    cout << i << endl;

but can we force i to be a iterator, I mean write a code that is equivalent to:

for (auto i = S.begin(); i != S.end(); i++)
    cout << (i != s.begin()) ? " " : "" << *i;

or could we do something that we can understand the index of i in the set(or vector)?

and another question is how could we say that don't do this for all elements in S but for first half of them or all of them except the first one.

or when we have a vector<int> V, and want to print its first n values what should we do? I know we can create a new vector but it takes time to copy a vector to a new vector.

Farzam
  • 1,272
  • 3
  • 14
  • 25
  • I have asked a simillar question here: http://stackoverflow.com/questions/8960403/get-first-n-elements-in-a-c-multiset (for multiset instead) – XCS Jan 25 '12 at 16:06
  • 1
    To print the first n values of a `vector`: `for (auto val : vec | sliced(0,n)) {...}`. See [`sliced`, from Boost.Range](http://www.boost.org/doc/libs/release/libs/range/doc/html/range/reference/adaptors/reference/sliced.html). – Luc Touraille Jan 25 '12 at 16:33
  • 1
    The purpose of the range-based `for` is to be able to more easily iterate over an entire range. The purpose is *not* to replace non-range `for` entirely. There are things for which you need regular `for`; the range `for` is just syntactic sugar for a common iteration pattern. It won't cover everything and it isn't supposed to. – Nicol Bolas Jan 25 '12 at 16:37
  • @LucTouraille: but it is not in the c++11 standard. – Farzam Jan 25 '12 at 17:56
  • IMO, you'd be better off using `std::copy` with an [`infix_ostream_iterator`](http://stackoverflow.com/questions/3496982/printing-lists-with-commas-c/3497021#3497021). – Jerry Coffin Jan 25 '12 at 19:12

6 Answers6

20

No, unluckily. See what the standard says:

The range-based for statement for ( for-range-declaration : expression ) statement is equivalent to

{
    auto && __range = ( expression );
    for ( auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin ) {
        for-range-declaration = *__begin;
        statement
    }
}

where __range, __begin, and __end are variables defined for exposition only

In other words, it already iterates from begin to end and already dereferences the iterator, which you never get to see.

moswald
  • 11,491
  • 7
  • 52
  • 78
Damon
  • 67,688
  • 20
  • 135
  • 185
  • Interesting, that means that __begin and __end must be of the same type. – bcmpinc Mar 04 '14 at 11:46
  • @bcmpinc: But could they ever be of different type? You can only iterate over an array or over a templated container (well no, technically you can iterate over anything that ::begin() and ::end() have overloads for... but you get what I mean), so all elements including __begin and __end are of the same type anyway. They might be pointers to derived classes, yes... but even that is the same type, even if one or the other pointer points at "something different". – Damon Mar 04 '14 at 12:34
  • 2
    With a filtering forward-iterator (that iterates over a collection, but only visits values that satisfy a predicate) it is much easier to for the iterator to check whether it is finished (it has to do so anyway) than to compare it to an end-iterator. By defining end() to return some different dummy type, operator!= can be implemented such that it only checks whether the iterator is finished. (I recently implemented such iterator.) – bcmpinc Mar 04 '14 at 12:49
9

The principle of the range-based for is to iterate over the whole range.

However you decide what the range is, therefore you can operate on the range itself.

template <typename It>
class RangeView {
public:
  typedef It iterator;

  RangeView(): _begin(), _end() {}
  RangeView(iterator begin, iterator end): _begin(begin), _end(end) {}

  iterator begin() const { return _begin; }
  iterator end() const { return _end; }

private:
  iterator _begin;
  iterator _end;
};

template <typename C>
RangeView<typename C::iterator> rangeView(C& c, size_t begin, size_t end) {
  return RangeView<typename C::iterator>(
           std::next(c.begin(), begin),
           std::next(c.begin(), end)
         );
}

template <typename C>
RangeView<typename C::const_iterator> rangeView(C const& c, size_t begin, size_t end) {
  return RangeView<typename C::const_iterator>(
           std::next(c.begin(), begin),
           std::next(c.begin(), end)
         );
}

Okay, this seriously ressemble Boost.Range...

And now, let's use it!

for (auto i: rangeView(set, 1, 10)) {
  // iterate through the second to the ninth element
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • @Xeo: I wish we had more of them :) Unfortunately some views are simple (like this) but "transformation" views can get really complicated. They fit well in functional languages (with immutability) but the "reference" idea is harder than necessary to implement on transformations (binding to temporaries issues...). That and iterators soon get *fat*. – Matthieu M. Jan 25 '12 at 18:03
2

No, you can't.

for (... : ...)

is called for instead of foreach only for the reason of not introducing a new keyword. The whole point of foreach is a quick short syntax for iterating all elements without caring for their index. For all other situations there's simple for which serves its purpose quite effectively.

Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
2

You can't in a set. Use the traditional for syntax or maintain your own index counter.

You can in a vector or other container with a flat layout like std::array or a C-style array. Change it to use a reference.:

for (auto &i: S)

Then you can compare the address of i with the address of s[0] to get the index.

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
  • What's stopping you from doing the same with a `std::set`? `for(auto &i: S) { if (&i == &*S.begin()) { ` – MSalters Jan 25 '12 at 22:05
  • @MSalters: I think OP is asking for the index of any `i`, e.g. to detect if i was in the "first half" of the set. You are correct that you can test for the special-case of index 0. – Drew Dormann Jan 25 '12 at 22:30
  • I understood it was specifically the first, as the intent is to print `N-1` separators between `N` elements. – MSalters Jan 26 '12 at 09:11
  • @MSalters: It does seem to change focus halfway through the question. – Drew Dormann Jan 26 '12 at 14:22
2

For the general case, you'd have to use a seperate variable:

int i = 0;

for (auto x : s)
    cout << (i++ ? " " : "") << x << endl;

There are, of course, tricks for certain containers like vector, but none work for every container.

You would probably be better off using the plain for loop for this purpose.

Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
1

Range-based for is intended for simple cases. I'd expect to to mildly useful while protoyping something but would expect uses of it mostly gone long before things actually become a product. It may possibly useful to make life for beginners easier, but this is an area I can't judge (but what seems to drive a lot of the recent C++ discussions).

The only somewhat constructive approach could be to use an adapter which references the underlying range and whose begin() and end() methods adjust the iterator appropriately. Also note that you probably want to hoist any special handling of the first or last element out of the loop processing the bulk of the data. Sure, it is only another check followed by a correctly predicted branch vs. no check and less pollution of the branch prediction tables.

jogojapan
  • 68,383
  • 11
  • 101
  • 131
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380