为什么const_iterator不提供像reverse_iterator这样的基础?

时间:2015-10-29 10:11:08

标签: c++ stl iterator containers const-iterator

const_iterator为什么不提供const_iterator::base()函数来获取iterator之类的非常量reverse_iterator

考虑以下伪代码(比如几何算法):

std::container< point > universe;
auto it = std::cbegin(universe);
std::list< decltype(it) > interesting_subset = sieve(it, std::cend(universe));
auto structure = algorithm(interesting_subset);

其中universe是所有输入点。在sieve()之后,interesting_subset包含universe成员子集的迭代器。在algorithm()之后构建structure的结果interesting_subset,其中包含universe成员的引用(迭代器)。

最后,我想更改point s,包含结果structure(例如,移动它们)。但同样地,我希望在algorithm行动期间保护他们免受混淆,因此我使用std::cbegin / std::cend作为std::begin / std::end的反面。最后,我只有const_iterator个来源point的引用。

这是我希望出现在STL容器中的iterator std::container< T >::const_iterator::base() const成员函数的一个非常大的用例。

4 个答案:

答案 0 :(得分:3)

  

为什么const_iterator没有提供const_iterator :: base()函数,以获得像reverse_iterator那样的非const迭代器呢?

保持const安全。提供这样的功能将非常危险,如本文已详细讨论的那样。

  

最后,我想改变包含在结果结构中的点(比如说,移动它们)。但同样我想在算法操作期间保护它们免受modyfining的影响,因此我使用std :: cbegin / std :: cend作为std :: begin / std :: end的反义词。最后,我只有const_iterator对源点的引用。

嗯,你问base - 成员错了。当然它会解决你的问题,但正如所说,这太危险了。让我为你重新提出一个问题:

  

如果我有一个const_iterator对象和非const访问容器,我如何有效(在恒定时间内)获得iterator引用的对象?

这是一个花哨的技巧:

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

我不赞成这个伎俩。我是从https://stackoverflow.com/a/10669041/2079303找到的,他们归功于Howard Hinnant and Jon Kalb

正如该答案的评论中所讨论的,这个技巧适用于所有标准容器,但不一定适用于所有可能符合标准的第三方容器,因为它们不需要提供erase

就个人而言,我更希望标准容器具有非const成员函数,将给定的const_iterator转换为iterator,但它们不会,因此您需要解决它。

答案 1 :(得分:2)

出于同样的原因,没有从const T*转换为T*:const-correctness。

reverse_iterator的比较无效,因为&#34;反向迭代器&#34;之间的区别和#34;转发迭代器&#34;完全正交于&#34; const iterator&#34;和&#34;非const迭代器&#34;。后者有分歧;前者最终没有。

答案 2 :(得分:1)

It's an interesting question. You want to be able to protect the points while you reason about them, but return mutable references to them once they've been reasoned about.

As a result of reasoning about the points, what you're actually returning is their 'names' or 'identifiers'.

We could conceivably map them by name, and get sieve() to return a vector of relevant names. Such a name could simply be an address if we wanted to avoid the overhead of storing a formal name (unique number, text string etc).

If we use an address of a const object as a name, then of course to turn it back into a reference to a mutable object we would need a const_cast. This might be seen as one of the few times we might reasonably use a const cast. When doing so we should encapsulate it in a utility class in order to limit any fallout.

EDIT: rethought the solution. this one is now un-abusable by unruly client code.

#include <iostream>
#include <vector>

struct point { int x, y; };

inline std::ostream& operator<<(std::ostream& os, const point& p)
{
    os << "(" << p.x << ", " << p.y << " )";
    return os;
}

struct point_collection
{
    using container_type = std::vector<point>;

    point_collection(container_type points) : _points(std::move(points)) {}

    using const_iterator = const point*;
    using iterator = point*;

    const_iterator begin() const { return &*_points.begin(); }
    const_iterator end() const { return &*_points.end(); }

    // note that this function is only available on a non-const point_collection
    point& mutable_reference(const_iterator input)
    {
        // could put an assert in here to ensure that input is within our range
        return *const_cast<iterator>(input);
    }

    container_type _points;
};


std::vector<point_collection::const_iterator> sieve(point_collection::const_iterator first,
                                                    point_collection::const_iterator last)
{
    std::vector<point_collection::const_iterator> result;

    for ( ; first != last ; ++first )
    {
        if (first->x > 6)
            result.push_back(first);
    }
    return result;
}


point_collection make_a_universe()
{
    return {
        std::vector<point> {
            { 10, 10 },
            { 6, 6 }
        }
    };
}

auto main() -> int
{
    using namespace std;
    auto universe = make_a_universe();

    auto interesting = sieve(universe.begin(), universe.end());

    for (auto point_id : interesting) {
        auto& p = universe.mutable_reference(point_id);
        p.x = -p.x;
        cout << p << endl;
    }
    return 0;
}

expected output:

(-10, 10 )

答案 3 :(得分:0)

reverse_iterator不会改变底层对象是否为const,并且const_iterator与迭代器无关(除了可转换和引用的要求之外),因此您要比较苹果和橙子。 / p>

如果const_iterator确实可以通过base提供对非常量迭代器的访问,那么可以执行

之类的操作
auto const & a = std::vector<int>(20, 1);
auto it = std::cbegin(a);
*it.base() = 4;

标准允许这样做。

你做 - 原则上 - 想绕过const_iterator提供的防止修改的保护。但这就是const_iterator的意义所在。所以这不是一个好主意,也不可能发生,我想(并希望)。

虽然我认为这是一个XY Problem,但我回答了这个问题,而不是就如何正确地做到这一点提供指导。

-edit -

如果您希望const_iterator能够返回iterator,那么您需要iterator

你的意图似乎是

  1. const_iterator传递给sieve,其中sieve不允许更改元素。
  2. 将完全相同的迭代器传递给algorithm,允许它修改它们。
  3. 您需要来自同一个对象的两个不同的东西。 没有人使sieve的实现者不能使用it.base(),因此根本不能保证sieve不会更改元素。我再说一遍,问题的关键const_iterator

    如果有任何方式使用const_iterator更改内容,则只会破坏它们。