【问题标题】:Iterators with complex value type: confusion with value_type and reference具有复杂值类型的迭代器:与 value_type 和 reference 混淆
【发布时间】:2018-03-04 15:11:21
【问题描述】:

我想创建一个自定义迭代器包装器,例如enumerate:给定一对T 类型的迭代器,它将返回一个可迭代类型std::pair<const int, T&> 的迭代器,其中该对的第一个元素将采用值 0、1、2,依此类推。

我无法确定我的迭代器的 value_typereference 应该是什么。我想支持两种行为:

首先,引用底层序列的值:

for (auto& kv: enumerate(my_vec)) {
    kv.second = kv.first;
}

(有点像std::iota);

二、复制值:

std::vector<int> a{10, 20, 30};
auto copy = *enumerate(a).begin();
a[0] = 15;
std::cout << copy.first << " " << copy.second; // 0 10

我很困惑Iterator::operator*() 的返回类型应该是什么。如果是std::pair&lt;const int, T&amp;&gt;,那么在第二个示例中的值将不会被复制。如果是std::pair&lt;const int, T&gt;,那么在第一个示例中,不可能引用基础值。这种迭代器的value_typereferencepointer typedefs应该怎么做?

这是我实现它的尝试。它支持引用但不复制。

template<typename T>
struct Iterator {
    using TT = typename std::iterator_traits<T>::value_type;

    using value_type = std::pair<const int, TT>;
    using reference = std::pair<const int&, typename std::iterator_traits<T>::reference>;
    using pointer = value_type*;
    using iterator_category = std::forward_iterator_tag;
    using difference_type = std::ptrdiff_t;

    std::pair<int, T> it;
    Iterator(T iterator) : it(0, iterator) {}
    bool operator==(const Iterator& other) const { return it.second == other.it.second; }
    bool operator!=(const Iterator& other) const { return it.second != other.it.second; }
    reference operator*() { return { it.first, *it.second }; }
    Iterator& operator++() { ++it.first; ++it.second; return *this; }
};

附:我刚刚检查过,boost::adaptors::index 遇到了同样的问题,并且没有复制值。

【问题讨论】:

  • 您或许能够做到这一点,但这并不容易。 operator* 必须返回一个代理类(不是直接的pair)。该类的second 成员又将是一个代理类,存储对基础元素的引用及其原始值的副本。然后它将实现operator T()(返回原始值)以及operator=(将赋值转发给引用)。我质疑结果是否值得这么麻烦。
  • 不知道你为什么会有疑问。可迭代类型 std::pair&lt;const int, T&amp;&gt; 应使用 std::pair&lt;const int, T&amp;&gt; 作为值和 std::pair&lt;const int, T&amp;&gt; &amp; 作为参考。在第二个示例中,值将按预期复制。
  • @VTT 如果我的引用类型是std::pair&lt;const int, T&amp;&gt;&amp;,那么我应该将它存储在某个地方,不是吗?看来在这种情况下我会返回一个不好的临时参考。
  • @IgorTandetnik 看起来可行,但我确实应该考虑是否真的需要它。谢谢。
  • 你绝对应该把它存储在某个地方。另请注意,当用户只想检查值时,由于取消引用迭代器而返回新的(复制的值)可能会导致相当大的开销,并且会阻止迭代器使用不可复制的类型。

标签: c++ iterator


【解决方案1】:

这个问题类似于std::vector&lt;bool&gt;的问题,你想提供一个代理,它的作用就像一个引用但又支持值语义。

但不同的是,所涉及的类型不受限制,涉及两个引用,并且会弹出各种毛羽。以下是部分实现,它说明了您遇到的一些问题

#include<iterator>
#include<functional>

template<typename F, typename S, bool defined = true>
struct sfinae_difference_type {};

template<typename F, typename S>
struct sfinae_difference_type<F, S, 
        std::is_same_v<typename std::iterator_traits<F>::difference_type, 
                       typename std::iterator_traits<S>::difference_type>>
{
    using difference_type = typename std::iterator_traits<F>::difference_type;
};

template<typename F, typename S>
class pair_iterator : sfinae_difference_type<F, S>
{
    using Fvalue_type = typename std::iterator_traits<F>::value_type;
    using Svalue_type = typename std::iterator_traits<S>::value_type;
    using Freference = typename std::iterator_traits<F>::reference;
    using Sreference = typename std::iterator_traits<S>::reference;

    F f;
    S s;

public:
    using value_type = std::pair<Fvalue_type, Svalue_type>;

    struct reference
    {
        Freference first;
        Sreference second;

        reference() = delete;
        reference(const reference& other) : first{other.first}, second{other.second} {} 
        reference& operator=(const reference& rhs)
        {
            first = rhs.first;
            second = rhs.second;
            return *this;
        }
        operator value_type() { return {f, s}; }

    private:
        reference(Freference f, Sreference s) : first{f}, second{s} {}
        friend pair_iterator;
    };

    struct pointer
    {
        // similar to reference
    };

    pair_iterator() = default;
    pair_iterator(const pair_iterator&) = default;
    pair_iterator(F f, S s) : f{f}, s{s} {}
    pair_iterator& operator++() { ++f; ++s; return *this; }
    reference operator*() { return {*f, *s}; }
    pointer operator->() { return {f.operator->(), s.operator->()}; }
    bool operator==(const pair_iterator& other)
    {
        return f == other.f && s == other.s;
    }
};

然后你把它当作

#include<vector>
#include<list>
#include<iostream>

int main()
{
    std::vector v{1, 2, 3, 4, 5};
    std::list l{6, 7, 8, 9, 10};
    pair_iterator begin{v.begin(), l.begin()}, end{v.end(), l.end()};
    for(; begin != end; ++begin)
        std::cout << begin->first << ' ' << begin->second << '\n';
}

Live

一些显而易见的问题:

  1. 实施繁琐。拥有对 sfinae 友好的类型别名和适当的代理需要大量样板文件。
  2. 代理的语义可能会令人困惑。将一个 reference 复制/分配给另一个是什么意思? auto is_this_a_copy = *it 应该做什么?
  3. 平等是什么意思?两个内部迭代器是否必须相等才能相等?这打破了与结束迭代器的比较。

所有这些都必须敲定才能发挥作用,而且没有一个简单的答案。

【讨论】:

  • 谢谢,这看起来很有趣。关于指针的另一个问题:据我了解,在此实现中,我们希望将 reference 实例存储在 pointer 类中并在 operator-&gt;() 中返回 &amp;reference。那是对的吗?如果是这样,您为什么将 addressof(*f) 而不是 *f 传递给指针的构造函数?
  • @IvanSmirnov 我弄错了,应该是f.operator-&gt;(),以避免获取临时代理引用的地址。 pointer 应该是可空的,并且在有意义的情况下支持算术,这两者都不能使用引用来实现
猜你喜欢
  • 2010-11-20
  • 1970-01-01
  • 2019-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多