【问题标题】:Why does libstdc++ implement both <L,R> and <LR> overloads for binary operators on iterators?为什么 libstdc++ 为迭代器上的二元运算符实现 <L,R> 和 <LR> 重载?
【发布时间】:2021-04-01 08:41:35
【问题描述】:

libstdc++3 中,在头文件 bits/stl_iterator.h(GCC 10 源代码here)中,__normal_iterator 的每个二元运算符都有两个重载已定义(这里以== 为例):

  template<typename _IteratorL, typename _IteratorR, typename _Container>
    _GLIBCXX20_CONSTEXPR
    inline bool
    operator==(const __normal_iterator<_IteratorL, _Container>& __lhs,
           const __normal_iterator<_IteratorR, _Container>& __rhs)
    _GLIBCXX_NOEXCEPT
    { return __lhs.base() == __rhs.base(); }

  template<typename _Iterator, typename _Container>
    _GLIBCXX20_CONSTEXPR
    inline bool
    operator==(const __normal_iterator<_Iterator, _Container>& __lhs,
           const __normal_iterator<_Iterator, _Container>& __rhs)
    _GLIBCXX_NOEXCEPT
    { return __lhs.base() == __rhs.base(); }

在这种情况下,.base() 返回指向数组元素的指针。这在整个库中也针对其他迭​​代器类型完成。根据散布在各处的 cmets 和变更日志,这样做是为了支持 iterators 和 const_iterators 之间的互操作性。

我的问题是,为什么 &lt;_IteratorL, _IteratorR, _Container&gt;&lt;_Iterator, _Container&gt; 都为它们定义了重载?也就是说,为什么需要&lt;_Iterator, _Container&gt;?前者不会涵盖所有情况吗?如果后者被删除会破坏什么?

GCC 的 libstdc++ 实现有很多街头信誉,所以我确信有一个很好的,可能是微妙的原因,但我不知道它可能是什么。

我之所以问,是因为我目前正在解决我自己的自定义迭代器实现中的一些问题,并将 STL 视为一个模型。

【问题讨论】:

  • 第二个重载允许隐式转换...第一个可能由于推导失败而导致转换失败(如果它是用户定义的转换)。
  • @dyp 我想我理解(也许?原因与this 相同吗?)除了我在构建失败案例时遇到问题(我希望this 不会编译...但确实如此,我想不出还有什么可以尝试的)。
  • My second attempt 也失败了。)
  • 看来我错了:godbolt.org/z/9fGvja -- 我认为这会起作用(扣除会失败,但过载仍然可行)。现在我很困惑:)

标签: c++ templates stl iterator libstdc++


【解决方案1】:

上面的评论抱怨std::rel_ops,它提供了template&lt;class T&gt; bool operator!=(const T&amp; lhs, const T&amp; rhs)

__normal_iterator 简化为显示问题的基本要素,我们得到这个:

#include <utility>

template<typename T>
struct normal_iterator {
    T m_base;

    const T& base() const { return m_base; }
};

// (1)
template<typename IteratorL, typename IteratorR>
bool operator!=(const normal_iterator<IteratorL>& lhs, const normal_iterator<IteratorR>& rhs) {
    return lhs.base() != rhs.base();
}

// (2)
template<typename Iterator>
bool operator!=(const normal_iterator<Iterator>& lhs, const normal_iterator<Iterator>& rhs) {
    return lhs.base() != rhs.base();
}

int main() {
    using namespace std::rel_ops;
    // Your container's `const_iterator` is `const int*`, and `iterator` is `int*`
    normal_iterator<const int*> a{nullptr};
    normal_iterator<int*> b{nullptr};
    a != b;  // Uses (1) to compare const_iterator and iterator

    a != a;  // Uses (2) to compare two iterators
}

如果没有第二个重载,这将无法编译,因为有两个可行的函数可以调用:

std::rel_ops::operator!=<normal_iterator<int*>>(const normal_iterator<int*>&, const normal_iterator<int*>&)

operator!=<int*, int*>(const normal_iterator<int*>&, const normal_iterator<int*>&)

两者都不比另一个更专业(这是模棱两可的)


对于std::rel_ops,没有理由让== 过载,但没有什么能阻止用户在其他命名空间中编写类似的template&lt;typename T&gt; bool operator==(const T&amp;, const T&amp;)

【讨论】:

  • 哦;我想知道那条评论是关于什么的(if anybody is curious)。好的,只是为了确保我理解;它没有完成,因为它本身在技术上是必要的,如果在其他地方定义了同样专业的选项,它是作为防御性的先发制人保护来防止歧义,这样它就不会干扰。正确的?这也解释了为什么我一直很难构建一个失败案例。
  • @JasonC 引入它的(19 岁)提交(gcc.gnu.org/git/…)似乎是gcc.gnu.org/bugzilla/show_bug.cgi?id=3628 的错误修复。所以是的,这似乎不是完全必要的,并且允许符合标准的标准库与using namespace std::rel_ops 有这种歧义(你可以在命名空间中有一个非模板bool operator==(std::vector&lt;int&gt;::iterator, std::vector&lt;int&gt;::iterator) { return false; }。这不会使std::vector&lt;int&gt;::iterator 不再一个迭代器)
猜你喜欢
  • 2011-10-18
  • 2020-05-11
  • 1970-01-01
  • 2013-09-07
  • 2016-02-03
  • 2020-06-14
  • 1970-01-01
  • 1970-01-01
  • 2013-07-30
相关资源
最近更新 更多