简体   繁体   English

如何删除 const_iterator 的常量性?

[英]How to remove constness of const_iterator?

As an extension to this question Are const_iterators faster?作为这个问题的扩展Are const_iterators faster? , I have another question on const_iterators . ,我还有一个关于const_iterators的问题。 How to remove constness of a const_iterator ?如何删除const_iterator的常量性? Though iterators are generalised form of pointers but still const_iterator and iterator s are two different things.尽管迭代器是指针的通用形式,但const_iteratoriterator仍然是两个不同的东西。 Hence, I believe, I also cannot use const_cast<> to covert from const_iterator to iterator s.因此,我相信,我也不能使用const_cast<>const_iterator转换为iterator s。

One approach could be that you define an iterator which moves 'til the element to which const_iterator points.一种方法可能是您定义一个迭代器,它移动直到const_iterator指向的元素。 But this looks to be a linear time algorithm.但这看起来是一个线性时间算法。

Any idea on what is the best way to achieve this?关于什么是实现此目标的最佳方法的任何想法?

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:在 C++11 中有一个具有恒定时间复杂度的解决方案:对于任何序列、关联或无序关联容器(包括所有标准库容器),您可以使用空范围调用范围擦除成员函数:

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 . range-erase 成员函数有一对const_iterator参数,但它们返回一个iterator Because an empty range is provided, the call to erase does not change the contents of the container.因为提供了一个空范围,所以对erase 的调用不会改变容器的内容。

Hat tip to Howard Hinnant and Jon Kalb for this trick. 向霍华德·辛南特和乔恩·卡尔布致敬这个技巧。

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.其中 iter 和 constIter 是合适的 typedef, d 是您正在迭代的容器。

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.在您上一篇文章的答案中,有几个人(包括我在内)出于与性能无关的原因推荐使用 const_iterators。 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.可读性、从设计板到代码的可追溯性……使用 const_iterators 提供对非常量元素的可变访问比根本不使用 const_iterators 要糟糕得多。 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.仅仅使用 const 来抛弃它比根本不使用 const 更糟糕。

If you are sure you want it, the good/bad part of C++ is that you can always get enough rope to hang yourself.如果你确定你想要它,C++ 的好/坏部分是你总能得到足够的绳子来吊死自己。 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.如果您打算使用 const_iterator 来解决性能问题,那么您真的应该重新考虑一下,但是如果您仍然想放手一搏……那么 C++ 可以提​​供您选择的武器。

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).首先,最简单的:如果您的操作将参数作为常量(即使在内部应用 const_cast),我相信它应该在大多数实现中直接工作(即使它可能是未定义的行为)。

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.如果你不能改变函子,那么你可以从任何一方解决这个问题:在 const 迭代器周围提供一个非常量迭代器包装器,或者在非常量函子周围提供一个 const 函子包装器。

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

Scott Meyer's article on preferring iterators over const_iterators answers this. Scott Meyer关于首选迭代器而不是 const_iterators的文章回答了这个问题。 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. Visage 的答案是唯一安全的 pre-C++11 替代方案,但实际上是实现良好的随机访问迭代器的恒定时间,而其他的则是线性时间。

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.我做的最简单的方法是 const_cast 返回的引用。

Something like this像这样的东西

const_cast<T&>(*it);

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.但我认为有时这种转换是不可能的,因为您的算法无法访问容器。

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.您可以从 const_iterator 中减去 begin() 迭代器以获得 const_iterator 指向的位置,然后将 begin() 添加回该位置以获得非常量迭代器。 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.编辑:这似乎只适用于线性(随机访问)容器。

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.我认为想出一个解决方案来解决这个问题会很有趣,该解决方案适用于不在标准库中且不包含 erase() 方法的容器。

Attempting to use this causes Visual Studio 2013 to hang on compile.尝试使用它会导致 Visual Studio 2013 在编译时挂起。 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().即使 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

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:假设您的容器的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 }
}

you can convert your const iterator value pointer to a non const value pointer and use it directly something like this您可以将 const 迭代器值指针转换为非 const 值指针并直接使用它,如下所示

    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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM