为什么 libstdc++ 为迭代器上的二元运算符同时实现 <L,R> 和 <LR> 重载?

Why does libstdc++ implement both <L,R> and <LR> overloads for binary operators on iterators?

libstdc++3中,在headerbits/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() returns 在这种情况下指向数组元素的指针。这在整个库中也适用于其他迭代器类型。根据散布在各处的评论和变更日志,这样做是为了支持 iterators 和 const_iterators 之间的互操作性。

我的问题是,为什么要为它们全部定义 <_IteratorL, _IteratorR, _Container><_Iterator, _Container> 重载?也就是说,为什么 <_Iterator, _Container> 是必要的?前者不会涵盖所有情况吗?如果删除后者会破坏什么?

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

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

上面抱怨 std::rel_ops 的评论提供了 template<class T> bool operator!=(const T& lhs, const T& 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<typename T> bool operator==(const T&, const T&)