Как убрать константность const_iterator?
В качестве продолжения этого вопроса const_iterators
Быстрее? У меня есть еще один вопрос const_iterators
, Как убрать константность const_iterator
? Хотя итераторы являются обобщенной формой указателей, но все же const_iterator
а также iterator
Это две разные вещи. Следовательно, я считаю, что я также не могу использовать const_cast<>
скрыться от const_iterator
в iterator
s.
Одним из подходов может быть то, что вы определяете итератор, который перемещает const_iterator
точки. Но это выглядит как линейный алгоритм времени.
Любая идея о том, как лучше всего достичь этого?
10 ответов
Существует решение с постоянной сложностью времени в C++11: для любой последовательности, ассоциативного или неупорядоченного ассоциативного контейнера (включая все контейнеры стандартной библиотеки) вы можете вызвать функцию-член range-erase с пустым диапазоном:
template <typename Container, typename ConstIterator>
typename Container::iterator remove_constness(Container& c, ConstIterator it)
{
return c.erase(it, it);
}
Функции-члены стирания диапазона имеют пару const_iterator
параметры, но они возвращают iterator
, Поскольку предоставляется пустой диапазон, вызов стирания не изменяет содержимое контейнера.
Шляпная подсказка Говарду Хиннанту и Джону Калбу за этот трюк.
К сожалению, линейное время - единственный способ сделать это:
iter i(d.begin());
advance (i,distance<ConstIter>(i,ci));
где iter и constIter являются подходящими typedefs, а d - контейнер, для которого вы выполняете итерацию.
В ответах на ваш предыдущий пост было несколько человек, включая меня, которые рекомендовали вместо этого использовать 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
};
Статья Скотта Майера о предпочтении итераторов, а не const_iterators отвечает на это. Ответ Visage - единственная безопасная альтернатива до C++11, но на самом деле это постоянное время для хорошо реализованных итераторов произвольного доступа и линейное время для других.
Это может быть не тот ответ, который вы хотели, но несколько связанный.
Я предполагаю, что вы хотите изменить то, на что указывает итератор. Простейший способ сделать это - вместо этого const_cast вернуть ссылку.
Что-то вроде этого
const_cast<T&>(*it);
Я считаю, что это преобразование не нужно в хорошо разработанной программе.
Если вам нужно сделать это - попробуйте изменить код.
В качестве обходного пути вы можете сделать следующее:
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 ) );
Но я думаю, что иногда это преобразование невозможно, потому что ваши алгоритмы могут не иметь доступа к контейнеру.
Вы можете вычесть итератор 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;
РЕДАКТИРОВАТЬ: кажется, работает только для линейных (произвольного доступа) контейнеров.
Я подумал, что было бы забавно придумать решение для этого, которое работает для контейнеров, которые отсутствуют в стандартной библиотеке и не включают в себя метод 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
Вы можете преобразовать свой указатель значения константного итератора в неконстантный указатель значения и использовать его непосредственно как-то так
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;
Предполагая, что ваш контейнер
const_iterator
имеет ту же компоновку, что и его
iterator
(действительное предположение для всех контейнеров STL), вы можете просто преобразовать первое во второе:
#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 }
}