【问题标题】:Can I use std::nth_element with ValueSwappable iterator but non MoveConstructible dereferenced value?我可以使用带有 ValueSwappable 迭代器但非 MoveConstructible 取消引用值的 std::nth_element 吗?
【发布时间】:2016-08-31 15:49:26
【问题描述】:

根据this documentation of std::nth_element,RandomIt 必须满足以下约束:

-RandomIt必须满足ValueSwappable和RandomAccessIterator的要求。

-解引用的RandomIt的类型必须满足MoveAssignable和MoveConstructible的要求。

考虑到执行 std::nth_element 的算法如何工作(即它们通常只在使用 swap(*it1, *it2) 的取消引用迭代器上工作),对于 RandomIt 的 ValueSwappable,第二个要求似乎是多余的。第二个要求保证 std::swap 如果没有定义特定类型的交换将起作用。

就我而言,我想创建一个由两个迭代器组成的迭代器,其行为就像所有操作都应用于两个迭代器一样。尽管满足 ValueSwappable 约束没有问题,但我看不出如何满足 MoveConstructible 和 MoveAssignable 的要求。

这是一个这样的迭代器的例子:

template <typename ItA, typename ItB>
struct pair_iterator {

    using VA = typename std::iterator_traits<ItA>::value_type;
    using VB = typename std::iterator_traits<ItB>::value_type;

    ItA a;
    ItB b;
    pair_iterator &operator++() { ++a; ++b; return *this; }
    pair_iterator &operator--() { --a; --b; return *this; }
    friend long operator-(const pair_iterator &iterator1, const pair_iterator &iterator2) {
        return iterator1.a-iterator2.a;
    }

    friend pair_iterator operator+(const pair_iterator &it, size_t increment) {
        return pair_iterator{it.a+increment, it.b+increment};
    }
    friend pair_iterator operator-(const pair_iterator &it, size_t increment) {
        return pair_iterator{it.a-increment, it.b-increment};
    }
    bool operator==(const pair_iterator &it) const { return it.a == a && it.b == b; }
    bool operator!=(const pair_iterator &it) const { return it.a != a || it.b != b; }
    bool operator>=(const pair_iterator &it) const { return a >= it.a; }
    bool operator<=(const pair_iterator &it) const { return a <= it.a; }
    bool operator<(const pair_iterator &it) const { return a < it.a; }
    bool operator>(const pair_iterator &it) const { return a > it.a; }

    struct Value {
        VA &a;
        VB &b;
        friend void swap(const Value &l, const Value &r) {
            using std::swap;
            swap(l.a, r.a);
            swap(l.b, r.b);
        }
    };

    Value operator*() const { return Value{*a, *b}; }

    using difference_type = long;
    using value_type = Value;
    using pointer = void;
    using reference = Value;
    using iterator_category = std::random_access_iterator_tag;
};

值类型 (Value) 不可移动赋值(或以任何方式赋值),因为它包含引用。

请注意,使用 clang++ 的 std::nth_element 对这种迭代器进行测试确实有效,但如果按照我的理解相信上述要求,C++ 库的不同实现可能会破坏我的代码。

我错过了什么吗?

【问题讨论】:

  • libstdc++ 的实现肯定需要两者。
  • 使用 g++4.9 进行的快速测试表明您是对的。但为什么要这样做呢?除了定义 swap() 之外,真的不需要任何东西。
  • 考虑到你的迭代器甚至不是一个输入迭代器(尽管它的标签是谎言),我不明白为什么实现会向后弯腰来支持它。在 Ranges TS 之前,通常不支持代理迭代器。
  • 我的(修改后的)问题是为什么需要来自迭代器的东西,而这些东西对于实现来说真的不需要?它可能会使实现更简单,但坦率地说,这不是必需的。只需要一个交换操作。添加需求会降低库的用处。

标签: c++ algorithm c++11


【解决方案1】:

这是上述迭代器的修改版本,可与 libstdc++ 一起使用。

template <typename ItA, typename ItB>
struct pair_iterator {

        using VA = typename std::iterator_traits<ItA>::value_type;
        using VB = typename std::iterator_traits<ItB>::value_type;

        ItA a;
        ItB b;
        pair_iterator &operator++() { ++a; ++b; return *this; }
        pair_iterator &operator--() { --a; --b; return *this; }
        friend long operator-(const pair_iterator &iterator1, const pair_iterator &iterator2) {
                return iterator1.a-iterator2.a;
        }

        friend pair_iterator operator+(const pair_iterator &it, size_t increment) {
                return pair_iterator{it.a+increment, it.b+increment};
        }
        friend pair_iterator operator-(const pair_iterator &it, size_t increment) {
                return pair_iterator{it.a-increment, it.b-increment};
        }
        bool operator==(const pair_iterator &it) const { return it.a == a && it.b == b; }
        bool operator!=(const pair_iterator &it) const { return it.a != a || it.b != b; }
        bool operator>=(const pair_iterator &it) const { return a >= it.a; }
        bool operator<=(const pair_iterator &it) const { return a <= it.a; }
        bool operator<(const pair_iterator &it) const { return a < it.a; }

        struct ValueCopy {
                VA a;
                VB b;
        };
        struct Value {
                VA &a;
                VB &b;
                friend void swap(const Value &l, const Value &r) {
                        using std::swap;
                        swap(l.a, r.a);
                        swap(l.b, r.b);
                }
                Value &operator=(Value&&v) {
                        a = v.a;
                        b = v.b;
                        return *this;
                }
                Value &operator=(const ValueCopy &v) {
                        a = v.a;
                        b = v.b;
                        return *this;
                }
                operator ValueCopy() const { return ValueCopy{a,b}; }
        };


        Value operator*() { return {*a, *b}; }

        using difference_type = long;
        using value_type = ValueCopy;
        using pointer = void;
        using reference = Value;
        using iterator_category = std::random_access_iterator_tag;
};

这里的技巧是定义 value_type 和 reference 以便 *it1 = *it2;有效,但 value v = *it; 也有效*it2 = v; 我仍然不确定这是否会一直有效。

PS:澄清为什么我想要一个“代理迭代器”,这是因为我想对一个向量执行 nth_element 操作,同时能够为数据向量构建一个旧索引的向量。我知道这可以通过在索引向量上使用比较运算符来完成,但经验表明,这种方法比同时对数据进行排序要慢得多,因为如果移动索引会发生缓存垃圾但不是数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-02
    • 2015-01-13
    • 2021-07-24
    • 2017-11-26
    • 2015-03-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多