Как убрать константность const_iterator? - PullRequest
41 голосов
/ 19 апреля 2009

Как продолжение этого вопроса Являются ли const_iterators быстрее? , у меня есть еще один вопрос по const_iterators. Как убрать константность const_iterator? Хотя итераторы являются обобщенной формой указателей, но все же const_iterator и iterator s - это две разные вещи. Следовательно, я считаю, что я также не могу использовать const_cast<> для конвертации из const_iterator в iterator с.

Одним из подходов может быть определение итератора, который перемещается до элемента, на который указывает const_iterator. Но это выглядит как линейный алгоритм времени.

Есть идеи, как лучше всего этого добиться?

Ответы [ 9 ]

66 голосов
/ 20 мая 2012

Существует решение с постоянной сложностью времени в C ++ 11: для любой последовательности, ассоциативного или неупорядоченного ассоциативного контейнера (включая все контейнеры стандартной библиотеки) вы можете вызвать функцию-член стирания диапазона с пустым диапазоном :

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

Функции-члены стирания диапазона имеют пару параметров const_iterator, но они возвращают iterator. Поскольку предоставляется пустой диапазон, вызов erase не изменяет содержимое контейнера.

Шляпная подсказка Говарду Хиннанту и Джону Калбу за этот трюк.

14 голосов
/ 19 апреля 2009

К сожалению, линейное время - единственный способ сделать это:

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

где iter и constIter являются подходящими typedefs, а d - это контейнер, по которому вы выполняете итерацию.

4 голосов
/ 19 апреля 2009

В ответах на ваш предыдущий пост было несколько человек, включая меня, которые рекомендовали вместо этого использовать const_iterators по причинам, не связанным с производительностью. Читаемость, прослеживаемость от платы разработки до кода ... Использование const_iterators для обеспечения изменяющегося доступа к неконстантному элементу намного хуже, чем никогда вообще не использовать const_iterators. Вы преобразуете свой код во что-то, что только вы поймете, с худшим дизайном и реальной болью в обслуживании. Использование const просто для того, чтобы отбросить его, гораздо хуже, чем вообще не использовать const.

Если вы уверены, что хотите этого, то хорошая / плохая часть C ++ в том, что вы всегда можете получить достаточно веревки, чтобы повеситься. Если вы намерены использовать const_iterator для проблем с производительностью, вам действительно следует переосмыслить его, но если вы все еще хотите отстрелить ногу ... хорошо, C ++ может предоставить ваше оружие выбора.

Во-первых, самое простое: если ваши операции принимают аргументы как const (даже если внутренне применяется const_cast), я считаю, что это должно работать напрямую в большинстве реализаций (даже если это, вероятно, неопределенное поведение).

Если вы не можете изменить функторы, то вы можете решить проблему с любой стороны: предоставить неконстантную обертку итератора вокруг константных итераторов или же предоставить обертку константного функтора вокруг неконстантных функторов.

Фасад итератора, длинный путь:

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
};
3 голосов
/ 19 апреля 2009

Статья Скотта Мейера о предпочтении итераторов, а не const_iterators отвечает на это. Ответ Visage - единственная безопасная альтернатива до C ++ 11, но на самом деле это постоянное время для хорошо реализованных итераторов произвольного доступа и линейное время для других.

3 голосов
/ 19 апреля 2009

Это может быть не тот ответ, который вы хотели, но несколько связанный.

Полагаю, вы хотите изменить то, на что указывает итератор. Самый простой способ сделать это - вместо этого const_cast вернуть ссылку.

Примерно так

const_cast<T&>(*it);

2 голосов
/ 19 апреля 2009

Я считаю, что это преобразование не нужно в хорошо разработанной программе.

Если вам нужно сделать это - попробуйте изменить код.

В качестве обходного пути вы можете сделать следующее:

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 ) );

Но я думаю, что иногда это преобразование невозможно, потому что ваши алгоритмы могут не иметь доступа к контейнеру.

1 голос
/ 19 апреля 2009

Вы можете вычесть итератор begin () из const_iterator, чтобы получить позицию, на которую указывает const_iterator, а затем добавить к ней begin (), чтобы получить неконстантный итератор. Я не думаю, что это будет очень эффективно для нелинейных контейнеров, но для линейных, таких как вектор, это займет постоянное время.

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;

РЕДАКТИРОВАТЬ : Это работает только для линейных контейнеров (произвольного доступа).

0 голосов
/ 15 января 2015

Я подумал, что было бы забавно придумать решение, которое работает для контейнеров, которых нет в стандартной библиотеке и которые не включают метод erase ().

Попытка использовать это приводит к зависанию Visual Studio 2013 при компиляции. Я не включаю тестовый пример, потому что предоставление информации читателям, которые могут быстро понять интерфейс, кажется хорошей идеей; Я не знаю, почему это зависает при компиляции. Это происходит, даже если const_iterator равен 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
0 голосов
/ 19 апреля 2009

вы можете преобразовать свой указатель значения константного итератора в неконстантный указатель значения и использовать его непосредственно как-то так

    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;
...