4

Why does const_iterator not provide a const_iterator::base() function, to obtain corresponding non-const iterator like reverse_iterator does?

Considering following pseudocode (say, geometric algorithm):

std::container< point > universe;
auto it = std::cbegin(universe);
std::list< decltype(it) > interesting_subset = sieve(it, std::cend(universe));
auto structure = algorithm(interesting_subset);

where universe is all the input points. After sieve()-ing the interesting_subset contains iterators to subset of universe's members. Following algorithm() constructs a resulting structure from interesting_subset, which consists of references (iterators) to members of the universe.

At the end, I want to change the points, containing into resulting structure (say, shift them). But equally I want to protect them from modyfining during algorithm action, and therefore I used std::cbegin/std::cend as opposite to std::begin/std::end. Finally I have only const_iterator references to source points.

This is a very use case for iterator std::container< T >::const_iterator::base() const member function I want to be present into STL containers.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169
  • 1
    What if the underlying container is `const`? – molbdnilo Oct 29 '15 at 10:14
  • @molbdnilo :) interesting question. Maybe runtime error (exception throwing)? Or maybe there shoud be two versions of `const_iterator` (say, the current should be replaced with `really_const_iterator` =). – Tomilov Anatoliy Oct 29 '15 at 10:15
  • @molbdnilo Maybe `std::cbegin(non_const_container)` should return augmented version of `const_iterator` having member function `base()`. – Tomilov Anatoliy Oct 29 '15 at 10:31
  • If `container` supports random access iterators, you can easily convert `it` to a non-const version using `auto offset = it - cbegin(universe); auto new_it = begin(universe) + offset;`. If it is not random access, this will be less efficient. – Bo Persson Oct 29 '15 at 10:31
  • @BoPersson Yes, it is. Also for any other container (if it consists of strictly different elements) I can find correspoinding element by means of `std::addressof(*cit) == std::addressof(*it)` comparison. But it results in additional quadratic complexity step to find all the coresponding elements. – Tomilov Anatoliy Oct 29 '15 at 10:35

4 Answers4

4

Why does const_iterator not provide a const_iterator::base() function, to obtain corresponding non-const iterator like reverse_iterator does?

To maintain const-safety. Providing such function would be very dangerous as already discussed here at length.

At the end, I want to change the points, containing into resulting structure (say, shift them). But equally I want to protect them from modyfining during algorithm action, and therefore I used std::cbegin/std::cend as opposite to std::begin/std::end. Finally I have only const_iterator references to source points.

Well, you're asking for the wrong thing with the base-member. Sure it would solve your problem, but as said, it's just too dangerous. Let me rephrease a question for you:

If I have a const_iterator to an object and non-const access to the container, how do I efficiently (in constant time) get an iterator to the referred object?

Here's a fancy trick to do exactly that:

template <typename Container, typename ConstIterator>
typename Container::iterator remove_constness(Container& c, ConstIterator it)
{
    return c.erase(it, it);
}

I don't take any credit for the trick. I found it from https://stackoverflow.com/a/10669041/2079303 and they credit Howard Hinnant and Jon Kalb

As discussed in the comments of that answer, this trick works with all standard containers, but not necessarily with all possible standard-compliant third-party containers because they're not required to provide erase.

Personally, I would prefer that standard containers had a non-const member function that would convert a given const_iterator to an iterator, but they don't, so you need to work around it.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
2

For the same reason that there is no conversion from const T* to T*: const-correctness.

Comparisons with reverse_iterator are not valid, because the distinction between "reverse iterator" and "forward iterator" is entirely orthogonal to the distinction between "const iterator" and "non-const iterator". The latter has ramifications; the former ultimately does not.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
1

It's an interesting question. You want to be able to protect the points while you reason about them, but return mutable references to them once they've been reasoned about.

As a result of reasoning about the points, what you're actually returning is their 'names' or 'identifiers'.

We could conceivably map them by name, and get sieve() to return a vector of relevant names. Such a name could simply be an address if we wanted to avoid the overhead of storing a formal name (unique number, text string etc).

If we use an address of a const object as a name, then of course to turn it back into a reference to a mutable object we would need a const_cast. This might be seen as one of the few times we might reasonably use a const cast. When doing so we should encapsulate it in a utility class in order to limit any fallout.

EDIT: rethought the solution. this one is now un-abusable by unruly client code.

#include <iostream>
#include <vector>

struct point { int x, y; };

inline std::ostream& operator<<(std::ostream& os, const point& p)
{
    os << "(" << p.x << ", " << p.y << " )";
    return os;
}

struct point_collection
{
    using container_type = std::vector<point>;

    point_collection(container_type points) : _points(std::move(points)) {}

    using const_iterator = const point*;
    using iterator = point*;

    const_iterator begin() const { return &*_points.begin(); }
    const_iterator end() const { return &*_points.end(); }

    // note that this function is only available on a non-const point_collection
    point& mutable_reference(const_iterator input)
    {
        // could put an assert in here to ensure that input is within our range
        return *const_cast<iterator>(input);
    }

    container_type _points;
};


std::vector<point_collection::const_iterator> sieve(point_collection::const_iterator first,
                                                    point_collection::const_iterator last)
{
    std::vector<point_collection::const_iterator> result;

    for ( ; first != last ; ++first )
    {
        if (first->x > 6)
            result.push_back(first);
    }
    return result;
}


point_collection make_a_universe()
{
    return {
        std::vector<point> {
            { 10, 10 },
            { 6, 6 }
        }
    };
}

auto main() -> int
{
    using namespace std;
    auto universe = make_a_universe();

    auto interesting = sieve(universe.begin(), universe.end());

    for (auto point_id : interesting) {
        auto& p = universe.mutable_reference(point_id);
        p.x = -p.x;
        cout << p << endl;
    }
    return 0;
}

expected output:

(-10, 10 )
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • `const_cast` is surely workaround (but ugly). Wrapper to iterator is very nice workaround (concept). It would be nice if STL have its implementation by itself. – Tomilov Anatoliy Oct 29 '15 at 11:41
  • @Orient stl already has the concept of a `map`. A `map` maps keys (names) to objects (values). All i've done in this case is taken a shortcut, but the principle is similar. – Richard Hodges Oct 29 '15 at 12:11
  • 'std::map< const_iterator, iterator >' have a memory cost as well as a runtime one, but doing the trivial thing. – Tomilov Anatoliy Oct 29 '15 at 12:32
  • 1
    if the universe was modelled by a `unordered_map` then you wouldn't need iterators at all - you could just reason about sets of idents. However, you're right there is a tradeoff between logical correctness and performance in this particular case. – Richard Hodges Oct 29 '15 at 12:48
  • @orient last comment. Updated the solution. This one is fully const-correct because it only allows a conversion of a const_ref through a mutable container. It's now optimally fast *and* safe. – Richard Hodges Oct 29 '15 at 13:08
1

A reverse_iterator does not change whether the underlying object is const or not and a const_iterator is not related to iterator (other than the requirement to be convertible and the referencing) so you're comparing apples and oranges.

If a const_iterator indeed would provide access to a non-const iterator via base it would be possible to do stuff like

auto const & a = std::vector<int>(20, 1);
auto it = std::cbegin(a);
*it.base() = 4;

The standard kindly permits this.

You do -in principle- want to circumvent the protection against modification which is provided by const_iterator. But that's what the const_iterator is about. So that is neither a good idea nor likely to happen, I think (and hope).

Eventhough I think this is a XY Problem, I answered to the question rather than providing guidance on how to do it properly.

-edit-

If you want a const_iterator to be able to return an iterator, it is iterator that you want.

Your intention seems to be

  1. Pass const_iterator to sieve where sieve is not allowed to change the elements.
  2. Pass the very same iterators to algorithm allowing it to modify them.

You require two different things from one and the same object. Nobody keeps the implementor of sieve from using it.base() so there would be no guarantee at all that sieve does not change the elements. This is, I repeat, the point of the matter const_iterator.

If there was any way of changing things using const_iterators it would just break them.

Community
  • 1
  • 1
Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
  • What about two different (but possilby compatible) types modeling the same semantic as `const_iterator` only differs by presence of `base()` member function? `const_iterator std::begin(T const &)` and `desired_const_iterator std::begin(T &)` overloadings possible. – Tomilov Anatoliy Oct 29 '15 at 11:35
  • WRT member functions. `std::container`s can provide non-const-qualified member function able to convert `const_iterator` to `iterator`. – Tomilov Anatoliy Oct 29 '15 at 11:38
  • That's not a problem to write: `template typename C::iterator iter_deconst(typename C::const_iterator iter, C &c) { return std::begin(c)+std::distance(std::cbegin(c), iter); }` – Pixelchemist Oct 29 '15 at 11:48
  • What about a wide kind of containers not supporting the random access iterators (`std::list`/`std::forward_list` and all the associative containers)? Even if I use `std::next` for forward iterators, then it would be additional linear complexity for each iterator from output of the whole algorithm. – Tomilov Anatoliy Oct 29 '15 at 11:53
  • About the text after --edit-- - you are totally wrong in interpretation of question's matter. – Tomilov Anatoliy Oct 29 '15 at 12:38
  • Sieve just make a set of const iterators to a subset of universe. algorithm uses the set resulting in an output, containing that const iterarors as a consistuent part. But further I want to modify the points from resulting structure after all. Noteably, I want to do it in the same scope, where the container is mutable, not const. – Tomilov Anatoliy Oct 29 '15 at 12:42
  • @Orient: So what is the point of using `const_iterator` at all, then? If not to prevent `sieve` from manipulating the elements? – Pixelchemist Oct 29 '15 at 14:45
  • `sieve()` is a excessive part of the question (I confess). Sure `sieve()` should accept container by const reference, but main point is user defined `algorithm()`, which accept a pair of iterators as an input, but must not modify the referenced by input iterators range, remaining generic. The `algorithm()`'s output is a construction, formed by input (const) iterators. – Tomilov Anatoliy Oct 29 '15 at 15:03
  • 1
    @Orient: So the point is that `algorithm` shall operate on a const container (const set of iterators) returning a structure that itself can modify the container? – Pixelchemist Oct 29 '15 at 15:06
  • Really *can't* but *should* modify. – Tomilov Anatoliy Oct 29 '15 at 15:59