How can I avoid the const_iterator trap when passing a constant container reference as a parameter

I generally prefer a constant, but recently ran into a conundrum with constant iterators that shakes my relationship to const. annoys me about them:

MyList::const_iterator find( const MyList & list, int identifier )
{
    // do some stuff to find identifier
    return retConstItor; // has to be const_iterator because list is const
}

      

The idea I'm trying to express here is of course that what is passed in the list cannot / will not be modified, but once I create a reference to the const list, I have to use a "const_iterator" which then prevents me from doing that -or with help , changing the result (which makes sense).

So, do I have to refuse to accept the const reference passed in the container, or am I missing another option?

This has always been my secret caveat about const: that even if you use it correctly, it can create problems that it shouldn't where there is no good / clean solution, although I understand that this is more specifically a problem between const and iterator.

Edit: I understand very well why you cannot and should not return a non-const iterator for a const container. My problem is that while I need a compile-time check for my container being passed by reference, I still want to find a way to pass the position of something and use that to change the non-continent version of the list.As mentioned in one of the answers, one can extract this position concept through "advance", but messy / ineffective.

+2


source to share


8 answers


If I understand what you are saying correctly, you are trying to use const

to indicate to the caller that your function will not modify the collection, but you want the caller (who may not have a const

reference to the collection) to be able to modify the collection with the return iterator. If so, I don't think there is a clean solution for this, unless the container provides a mechanism to turn the const

-terator into a non-t20> one (I don't know about the container that does this). Your best bet is for your function to take the link const

. You can also have 2 overloads of your function, one const

and one not const

, so that in the case of a caller who only has a link const

, they can still use your function.



+10


source


This is not a trap; this is a feature. (: -)

In general, you cannot return a non-console "handle" to your data from a const method. For example, the following code is illegal.

class Foo
   {
      public:
         int& getX() const {return x;}
      private:
         int x;
   };

      

If it was legal, then you could do something like this ....

   int main()
   {
      const Foo f;
      f.getX() = 3; // changed value of const object
   }

      

The STL designers followed this convention with constants.




In your case, what the const company would buy you is the ability to call it const in collections. In this case, you don't want the iterator to fall back to modification. But you want to allow modification of it if the collection is not const. Thus, you may need two interfaces:

MyList::const_iterator find( const MyList & list, int identifier )
{
    // do some stuff to find identifier
    return retConstItor; // has to be const_iterator because list is const
}

MyList::iterator find( MyList & list, int identifier )
{
    // do some stuff to find identifier
    return retItor; 
}

      

Or you can do it all with one template function

template<typename T>    
T find(T start, T end, int identifier);

      

It will then return a non-const iterator if the input iterators are not constants, and a const_iterator if they are constants.

+6


source


What I did with the wrapping standard algorithms, there is a meta object for defining the container type:

namespace detail
{
    template <class Range>
    struct Iterator
    {
        typedef typename Range::iterator type;
    };

    template <class Range>
    struct Iterator<const Range>
    {
        typedef typename Range::const_iterator type;
    };
}

      

This allows for a single implementation like find:

template <class Range, class Type>
typename detail::Iterator<Range>::type find(Range& range, const Type& value)
{
    return std::find(range.begin(), range.end(), value);
}

      

However, this does not allow for calling this with temporary (I suppose I can live with it).

In any case, in order to return a modifiable reference to the container, you apparently cannot guarantee what your function does or does not do with the container. So this noble principle is really destroyed: don't guess about it.

I suppose that the correctness of the constants is more of a favor to the caller of your functions, but rather that a certain measure for the nanny to ensure that you get the correct lookup function.


Another question is how would you feel if I defined the following predicate and then abused the standard find_if algorithm to increment all values ​​to the first value> = 3:

bool inc_if_less_than_3(int& a)
{
    return a++ < 3;
}

      

(GCC doesn't stop me, but I couldn't tell if some of the undefined behavior is pedantic).

1) The container is owned by the user. Since allowing modification through a predicate does not harm the algorithm in any way, it must be up to the caller to decide how they use it.

2) This is disgusting !!! Better to implement find_if in a way that avoids this nightmare (best to do since apparently you can't choose whether the iterator is const or not):

template <class Iter, class Pred>
Iter my_find_if(Iter first, Iter last, Pred fun)
{
    while (first != last 
       && !fun( const_cast<const typename std::iterator_traits<Iter>::value_type &>(*first)))
        ++first;
    return first;
}

      

+3


source


While I think your design is a bit confusing (since others have pointed out that iterators allow changes in the container, so I don't see that your function is actually const), there is a way to get an iterator from a const_iterator. The efficiency depends on the type of iterators.


#include <list>

int main()
{
  typedef std::list<int> IntList;
  typedef IntList::iterator Iter;
  typedef IntList::const_iterator ConstIter;

  IntList theList;
  ConstIter cit = function_returning_const_iter(theList);

  //Make non-const iter point to the same as the const iter.
  Iter it(theList.begin());
  std::advance(it, std::distance<ConstIter>(it, cit));

  return 0;
}

      

+2


source


Instead of trying to guarantee that the list is not modified using the const keyword, it is better to guarantee it with a postcondition in this case. In other words, tell the user through comments that the list will not be changed.

Better yet, use a template that can be created for iterators or const_iterators:

template <typename II> // II models InputIterator
II find(II first, int identifier) {
  // do stuff
  return iterator;
}

      

Of course, if you run into a problem like this, you can also expose MyList to iterators to the user and use std :: find.

+1


source


If you change the data directed by the iterator, you change the list.

The idea I am trying to express here is of course that what is passed in the list cannot / will not be modified, but once I create a reference to the const list I have to use a "cons_iterator" which then prevents me from doing anything with the result.

What is "dong anything"? Data change? This is a change to the list, which goes against your original intentions. If the list is const, it (and "it" includes its data) is constant.

If your function were to return a non-const iterator, that would create a way to modify the list, so the list would not be const.

+1


source


You're thinking the wrong way about your design. Don't use const arguments to specify what a function does - use them to describe an argument. It doesn't matter in this case that find () doesn't modify the list. The important thing is that find () is intended to be used with mutable lists.

If you want your function to find()

return a non-const iterator, it allows the caller to modify the container. It would be wrong accept a const container, as that would provide the caller with a hidden way to remove the container constant.

Consider:

// Your function
MyList::iterator find(const MyList& list, int identifier);

// Caller has a const list.
const MyList list = ...
// but your function lets them modify it.
*( find(list,3) ) = 5;

      

So, your function should take a non-const argument:

MyList::iterator find(MyList& list, int identifier);

      

Now when the caller tries to use your function to modify its const list, it will get a compilation error. This is a much better result.

+1


source


If you want to return non-content access to the container, make the function non-constant. You admit the possibility of changing the container as a side effect.

This is a good reason the standard algorithms accept iterators and not containers, so they can avoid this problem.

0


source







All Articles