57

As an extension to this question Are const_iterators faster?, I have another question on const_iterators. How to remove constness of a const_iterator? Though iterators are generalised form of pointers but still const_iterator and iterators are two different things. Hence, I believe, I also cannot use const_cast<> to covert from const_iterator to iterators.

One approach could be that you define an iterator which moves 'til the element to which const_iterator points. But this looks to be a linear time algorithm.

Any idea on what is the best way to achieve this?

Community
  • 1
  • 1
aJ.
  • 34,624
  • 22
  • 86
  • 128

10 Answers10

89

There is a solution with constant time complexity in C++11: for any sequence, associative, or unordered associative container (including all of the Standard Library containers), you can call the range-erase member function with an empty range:

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

The range-erase member functions have a pair of const_iterator parameters, but they return an iterator. Because an empty range is provided, the call to erase does not change the contents of the container.

Hat tip to Howard Hinnant and Jon Kalb for this trick.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 4
    You need access to the container, however. – Xeo May 20 '12 at 01:23
  • 14
    @xeo: Well, of course. It would be a gaping hole in const safety if you could do this without a non-const reference to the container. – James McNellis May 20 '12 at 01:46
  • 3
    +1. Ultra-pedantry: this works for all standard containers, since all standard containers are either sequences or associative containers or unordered associative containers. But `erase` isn't actually part of the container requirements, so it needn't necessarily work for all user-defined types that satisfy the container requirements. You've sort of already said this in the answer, but add "unordered associative" to the list in parens. Maybe this pedantry should be applied to your comment on Visage's answer, where you said "all containers", more than on your full answer. – Steve Jessop May 20 '12 at 15:51
  • 1
    @SteveJessop: Good point. I added unordered associative containers; I forgot that they aren't really "associative containers." – James McNellis May 20 '12 at 19:39
  • Btw, `std::array` is close to a counter-example, but not quite there. It almost satisfies the container requirements, and it's not too difficult to imagine a third-party container type a bit like an `array`, that truly is a container without `erase`. – Steve Jessop May 23 '12 at 10:43
  • 1
    It should be noted that the `erase` invocation implies potential iterators and references invalidation for some containers. Of course it shouldn't happen for empty ranges, but certain b̶r̶a̶i̶n̶-̶d̶e̶a̶d̶ implementations like VS2017's one can trigger an assertion error. – newbie Mar 12 '18 at 15:38
  • You forgot about forward_list. It is the special container that contains the same methods, but with "_after" suffix. Correct implementation for it would be: ```c++ auto after_i = i; return c.erase_after(i, ++after_i); ``` – Paweł Stankowski Mar 27 '20 at 22:08
16

Unfortunately linear time is the only way to do it:

iter i(d.begin());
advance (i,distance<ConstIter>(i,ci));

where iter and constIter are suitable typedefs and d is the container over which you are iterating.

pauljwilliams
  • 19,079
  • 3
  • 51
  • 79
  • 5
    Implementations are allowed to (and do) specialize std::advance and std::distance for random access iterators so that this can be constant time for some containers. – CB Bailey Apr 19 '09 at 10:26
  • 5
    Actually, this should be constant time for (well-implemented) random access iterators. See http://www.aristeia.com/Papers/CUJ_June_2001.pdf. – Pontus Gagge Apr 19 '09 at 13:22
  • For non-random-access iterators, I think `iter i(d.begin()); while (ConstIter(i) != ci) ++i;` would be more efficient. Still disappointing, but at least it only walks forwards from `i` once. You can use iterator-type-tag dispatch to write function templates that in effect overload on iterator type, at least they do assuming the iterators are tagged correctly. – Steve Jessop Mar 13 '12 at 17:53
  • 1
    There is a constant time solution that has well-defined behavior and works for all Standard Library containers (and most other containers); see the answer that I have just posted. – James McNellis May 20 '12 at 19:40
  • For `std::string`, make sure cast the string to a const string before calling `begin`. GCC's standard library implements copy on write and `begin` may cause the string to reallocate its memory which invalidates the iterators! – Jonathan Jansson Jan 12 '13 at 20:01
  • 1
    @JonathanJansson C++03 allowed the behavior you're talking about, but [C++11 (21.4.1#6) implicitly prohibits it](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf). The wording that in C++03 explicitly allowed `begin()` to invalidate iterators under certain circumstances has been removed, so that in C++11 `begin()` no longer invalidates iterators. – Quuxplusone Dec 01 '13 at 20:51
5

In the answers to your previous post, there were a couple of people, me included, that recommended using const_iterators instead for non-performance related reasons. Readability, traceability from the design board to the code... Using const_iterators to provide mutating access to a non-const element is much worse than never using const_iterators at all. You are converting your code into something that only you will understand, with a worse design and a real maintainability pain. Using const just to cast it away is much worse than not using const at all.

If you are sure you want it, the good/bad part of C++ is that you can always get enough rope to hang yourself. If your intention is using const_iterator for performance issues, you should really rethink it, but if you still want to shoot your foot off... well C++ can provide your weapon of choice.

First, the simplest: if your operations take the arguments as const (even if internally apply const_cast) I believe it should work directly in most implementations (even if it is probably undefined behavior).

If you cannot change the functors, then you could tackle the problem from either side: provide a non-const iterator wrapper around the const iterators, or else provide a const functor wrapper around the non-const functors.

Iterator façade, the long road:

template <typename T>
struct remove_const
{
    typedef T type;
};
template <typename T>
struct remove_const<const T>
{
    typedef T type;
};

template <typename T>
class unconst_iterator_type
{
    public:
        typedef std::forward_iterator_tag iterator_category;
        typedef typename remove_const<
                typename std::iterator_traits<T>::value_type
            >::type value_type;
        typedef value_type* pointer;
        typedef value_type& reference;

        unconst_iterator_type( T it )
            : it_( it ) {} // allow implicit conversions
        unconst_iterator_type& operator++() {
            ++it_;
            return *this;
        }
        value_type& operator*() {
            return const_cast<value_type&>( *it_ );
        }
        pointer operator->() {
            return const_cast<pointer>( &(*it_) );
        }
        friend bool operator==( unconst_iterator_type<T> const & lhs,
                unconst_iterator_type<T> const & rhs )
        {
            return lhs.it_ == rhs.it_;
        }
        friend bool operator!=( unconst_iterator_type<T> const & lhs,
                unconst_iterator_type<T> const & rhs )
        {
            return !( lhs == rhs );
        }
    private:
        T it_;  // internal (const) iterator
};
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
4

Scott Meyer's article on preferring iterators over const_iterators answers this. Visage's answer is the only safe pre-C++11 alternative, but is actually constant time for well-implemented random access iterators, and linear time for others.

Pontus Gagge
  • 17,166
  • 1
  • 38
  • 51
2

This may not be the answer you wanted, but somewhat related.

I assume you want to change the thing where the iterator points to. The simplest way I do is that const_cast the returned reference instead.

Something like this

const_cast<T&>(*it);

leiz
  • 3,984
  • 2
  • 23
  • 17
  • Some functions like erase etc. require a const_iterator, so this wouldn't work. – tstenner Apr 19 '09 at 10:38
  • You mean that erase takes a non const iterator, right? If that is the case, why do you use const_iterator at first place? most of time this kinda const cast I needed was for debugging propers. – leiz Apr 19 '09 at 10:49
2

I believe this conversion is not needed in a well-designed program.

If you need do this - try redesigning the code.

As workaround you can use the following:

typedef std::vector< size_t > container_type;
container_type v;
// filling container code 
container_type::const_iterator ci = v.begin() + 3; // set some value 
container_type::iterator i = v.begin();
std::advance( i, std::distance< container_type::const_iterator >( v.begin(), ci ) );

But I think that sometimes this conversion is impossible, because your algorithms don't have access to the container.

metamorphosis
  • 1,972
  • 16
  • 25
bayda
  • 13,365
  • 8
  • 39
  • 48
1

You can subtract the begin() iterator from the const_iterator to obtain the position the const_iterator is pointing to and then add begin() back to that to obtain a non-const iterator. I don't think this will be very efficient for non-linear containers, but for linear ones such as vector this will take constant time.

vector<int> v;                                                                                                         
v.push_back(0);
v.push_back(1);
v.push_back(2);
v.push_back(3);
vector<int>::const_iterator ci = v.begin() + 2;
cout << *ci << endl;
vector<int>::iterator it = v.begin() + (ci - v.begin());
cout << *it << endl;
*it = 20;
cout << *ci << endl;

EDIT: This appears to only work for linear (random access) containers.

moinudin
  • 134,091
  • 45
  • 190
  • 216
  • That will only work if you have a suitable operator defined for subtracting iterators from const iterators. AFAIK there is no such thing. – pauljwilliams Apr 19 '09 at 10:02
  • It might work for vector(Random Access iterator). It may not work for list and other container. – aJ. Apr 19 '09 at 10:07
  • 1
    @Visage: You don't need an suitable operator, in this case you are subtracting a const_iterator from a const_iterator, getting an integer offset, and adding it to an iterator. Perfectly valid, and works as it would be expected to work. – X-Istence Apr 19 '09 at 10:07
  • More specifically, this will only work with a Random Access Iterator since it is the concept that defines the necessary operations. Take a look at the SGI docs (http://www.sgi.com/tech/stl/RandomAccessIterator.html) for what I consider the best description. – D.Shawley Apr 19 '09 at 17:34
0

I thought it would be fun to come up with a solution to this that works for containers that aren't in the standard library and don't include the erase() method.

Attempting to use this causes Visual Studio 2013 to hang on compile. I'm not including the test case because leaving it to readers who can quickly figure out the interface seems like a good idea; I don't know why this hangs on compile. This occurs even when the const_iterator is equal to begin().

// deconst.h

#ifndef _miscTools_deconst
#define _miscTools_deconst

#ifdef _WIN32 
    #include <Windows.h>
#endif

namespace miscTools
{
    template < typename T >
    struct deconst
    {

        static inline typename T::iterator iterator ( typename T::const_iterator*&& target, T*&& subject )
        {
            typename T::iterator && resultant = subject->begin ( );

            bool goodItty = process < 0, T >::step ( std::move ( target ), std::move ( &resultant ), std::move ( subject ) );

        #ifdef _WIN32
             // This is just my habit with test code, and would normally be replaced by an assert
             if ( goodItty == false ) 
             {
                  OutputDebugString ( "     ERROR: deconst::iterator call. Target iterator is not within the bounds of the subject container.\n" ) 
             }
        #endif
            return std::move ( resultant );
        }

    private:

        template < std::size_t i, typename T >
        struct process
        {
            static inline bool step ( typename T::const_iterator*&& target, typename T::iterator*&& variant, T*&& subject )
            {
                if ( ( static_cast <typename T::const_iterator> ( subject->begin () + i ) ) == *target )
                {
                    ( *variant ) += i;
                    return true;
                }
                else
                {
                    if ( ( *variant + i ) < subject->end () )
                    {
                        process < ( i + 1 ), T >::step ( std::move ( target ), std::move ( variant ), std::move ( subject ) );
                    }
                    else { return false; }
                }
            }
        };
    };
}

#endif
JPHarford
  • 78
  • 1
  • 5
0

Assuming your container's const_iterator has the same layout as its iterator (a valid assumption for all STL containers), you can simply bit-cast the former to the latter:

#include <bit>
#include <vector>

void demo() {
    using vec_t = std::vector<int>;
    vec_t v { 1, 2, 3 };
    vec_t::const_iterator c_it = v.cbegin();
    vec_t::iterator it = std::bit_cast<vec_t::iterator>(c_it);
    *it = 4; // OK, now v holds { 4, 2, 3 }
}
Matt Whitlock
  • 756
  • 7
  • 10
0

you can convert your const iterator value pointer to a non const value pointer and use it directly something like this

    vector<int> v;                                                                                                         
v.push_back(0);
v.push_back(1);
v.push_back(2);
v.push_back(2);
vector<int>::const_iterator ci = v.begin() + 2;
cout << *ci << endl;
*const_cast<int*>(&(*ci)) = 7;
cout << *ci << endl;
Ankit
  • 653
  • 2
  • 8
  • 18
  • 1
    This "works" for `std::vector` and other containers with contiguous storage, but not for other containers (like `std::list`). – James McNellis May 19 '12 at 21:41