为什么C++11中删除了对pair范围的访问?

56
我刚刚发现,在某个时候,C++11草案中有std::pairstd::begin/std::end重载,允许将一对迭代器作为适用于范围基于for循环的范围(N3126,第20.3.5.5节),但此后已被删除。有人知道为什么会被删除吗?我认为这次删除非常不幸,因为似乎没有其他方法可以将一对迭代器视为范围。确实:
  • 范围基于for循环的查找规则表明,begin/end在1)范围对象的成员函数中查找2)在“关联命名空间”中查找自由函数
  • std::pair没有begin/end成员函数
  • std::pair<T,U>的唯一关联命名空间通常是命名空间std
  • 我们不允许自己重载std::begin/std::end以用于std::pair
  • 我们不能为std::pair专门化std::begin/std::end(因为专门化必须是部分的,对于函数来说是不允许的)
我是否遗漏了其他方法?

std::pair的begin()和end()不就是.first和.second吗? - Himadri Choudhury
@Himadri:是的,但要在范围for循环中使用它,我们需要begin()和end()。 - HighCommander4
2
可能是因为语义(一对不一定是一个范围)和避免歧义,例如在这里报告的:https://dev59.com/yG855IYBdhLWcg3wxHUq#4155508。 - sehe
3个回答

44

我认为Alisdair Meredith在2009年发表的论文 "Pairs do not make good ranges" 至少部分解答了这个问题。基本上,许多算法返回一对迭代器,实际上并不能保证它们是有效的范围。似乎他们因此从for-range循环中删除了对pair<iterator,iterator>的支持。然而,这种提议的解决方案尚未被完全采纳。

如果您确定某些迭代器对确实表示一个有效范围,则可以将它们包装到一个自定义类型中,该类型提供begin() / end()成员函数:

template<class Iter>
struct iter_pair_range : std::pair<Iter,Iter> {
    iter_pair_range(std::pair<Iter,Iter> const& x)
    : std::pair<Iter,Iter>(x)
    {}
    Iter begin() const {return this->first;}
    Iter end()   const {return this->second;}
};

template<class Iter>
inline iter_pair_range<Iter> as_range(std::pair<Iter,Iter> const& x)
{ return iter_pair_range<Iter>(x); }

int main() {
    multimap<int,int> mm;
    ...
    for (auto& p : as_range(mm.equal_range(42))) {
       ...
    }
}

(未经测试)

我同意这是一个小缺陷。返回有效范围的函数(如equal_range)应该使用适当的返回类型来说明。我们必须通过像上面的as_range一样手动确认这一点有些尴尬。


3
boost::range就是这样的。使用boost::range而不是手写代码可能更可取。[1]: http://www.boost.org/doc/libs/1_53_0/libs/range/doc/html/range/reference/utilities/iterator_range.html - Alexander Oh
1
Meredith只发现了5个受此问题影响的库函数。他们是否直接让这些函数保证有效范围,而不是扩展类型系统,会更容易些呢?我真的不确定这种新类型到底解决了什么问题;你仍然可以使用不生成有效范围的迭代器创建它的实例。 - Ben Collins
1
@BenCollins:我认为主要问题在于有很多现有的应用程序代码使用pair来完成其他功能(鉴于迭代器的临时性质,这可能是不寻常的,但是你可以想象一下将迭代器映射到迭代器的情况)。定义一个独立的类型作为迭代器范围可以使其目的清晰,并保证现有应用程序代码不会产生任何意外副作用。 - Miral
boost::make_iterator_range 为您执行相同的操作。我理解这段代码仅用于展示。 - Maxim Egorushkin

9
你可以使用 boost::make_iterator_range。它构造了一个具有 begin()end() 方法的 iterator_range。 boost::make_iterator_range 可以接受迭代器的 std::pair

6

利用C++11的优化进一步扩展上述答案:

#include <utility>

template<class Iter>
struct range_t : public std::pair<Iter, Iter> {
    using pair_t = std::pair<Iter, Iter>;
    range_t(pair_t&& src)
    : std::pair<Iter, Iter>(std::forward<pair_t>(src))
    {}

    using std::pair<Iter, Iter>::first;
    using std::pair<Iter, Iter>::second;

    Iter begin() const { return first; }
    Iter end() const { return second; }
};

template<class Iter>
range_t<Iter> range(std::pair<Iter, Iter> p) {
    return range_t<Iter>(std::move(p));
}

template<class Iter>
range_t<Iter> range(Iter i1, Iter i2) {
    return range_t<Iter>(std::make_pair(std::move(i1), std::move(i2)));
}


// TEST: 

#include <iostream>
#include <set>
using namespace std;

int main() {

    multiset<int> mySet { 6,4,5,5,5,3,3,67,8,89,7,5,45,4,3 };

    cout << "similar elements: ";
    for (const auto&i : range(mySet.lower_bound(5), mySet.upper_bound(10))) {
        cout << i << ",";
    }
    cout << "\n";

    int count = 0, sum = 0;
    for (const auto& i: range(mySet.equal_range(5)))
    {
        ++count;
        sum += i;
    }
    cout << "5 appears " << count << " times\n"
    << "the sum is " << sum << "\n";

return 0;
}

3
效果很好,只有一个小问题 - POSIX保留后缀“_t”。请参见:在C++标识符中使用下划线的规则是什么? - Eoin
3
诚实地说,POSIX声称对所有以“_t”结尾的标识符拥有所有权。请参阅GNU-保留名称。因此,POSIX认为自己可以在全局命名空间中声明带有该后缀的新类型,现在或将来的任何时候都可以这样做。POSIX不是C ++,因此新类型不会嵌套在“std”中,而是全局的。如果该类型与现有代码库中的类型冲突,则认为该代码库(一直)没有遵守规则。请注意,本译文力求忠实原意,同时尽可能简洁易懂,如有不当之处,请您谅解。 - Eoin
3
嗯,在这种情况下,POSIX与大部分Boost库和一些颇有影响的C++开发者存在分歧。我想,如果我们想要绝对安全,我们可以认为在除了全局命名空间之外的任何命名空间中,将"_t"添加到类型上是允许的-因为只有全局命名空间可能会受到C(因此是POSIX)代码的污染。 - Richard Hodges
2
这个“小问题”已经超出了预期的范围 ;-)。我完全同意很多开发者采用了这种约定。个人而言,我避免使用后缀,例如在采用您的解决方案时,我使用了struct rangemake_range作为自由函数。但这是为了更加谨慎而犯的错误。 - Eoin
2
这次讨论很有用 - 我学到了一些东西 :-) 我非常喜欢使用 _t 后缀,因为它可以以不显眼的方式断言名称是一个类型。在 C 和 C++ 中,我也很喜欢使用 lower_case_names,因为我发现它们比 camelCaps 更容易和自然阅读。 我还经常像这样命名函数对象:struct do_something_f; 来断言它是一个函数对象。 - Richard Hodges
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接