【问题标题】:How to write a C++11 template that can take a const iterator如何编写可以采用 const 迭代器的 C++11 模板
【发布时间】:2014-08-31 18:12:54
【问题描述】:

在回复 CodeReview 上的this question 时,我正在考虑如何编写一个模板函数来指示包含对象的const-ness。

具体来说,考虑这个模板化函数

#include <iostream>
#include <numeric>
#include <vector>

template <class It>
typename std::iterator_traits<It>::value_type average(It begin, It end) {
    typedef typename std::iterator_traits<It>::value_type real;
    real sum = real();
    unsigned count = 0;
    for ( ; begin != end; ++begin, ++count)
        sum += *begin;
    return sum/count;
}

int main()
{
    std::vector<double> v(1000);
    std::iota(v.begin(), v.end(), 42);
    double avg = average(v.cbegin(), v.cend());
    std::cout << "avg = " << avg << '\n';
}

它需要一个迭代器并根据包含的数字计算平均值,但保证不会通过传递的迭代器修改向量。如何将这一点传达给模板的用户?

注意,这样声明它:

template <class It>
typename std::iterator_traits<It>::value_type average(const It begin,
    const It end)

不起作用,因为它不是迭代器,而是迭代器指向的东西,即const。我是否必须等待concepts 标准化?

请注意,我不想要求 const 迭代器,而是表明它们可以 在这里安全使用。也就是说,我不想限制调用者,而是想传达我的代码正在做出的承诺:“我不会修改你的基础数据。”

【问题讨论】:

  • 你的average 函数应该接受常量和非常量迭代器。所以你不能 require 常量迭代器(某些迭代器类型可能没有对应的常量迭代器)。但是,您可以将迭代器包装到不允许修改的包装器中。
  • 此外,由于iterator_traits 不提供相应的常量迭代器类型,因此无法从需要非常量迭代器的函数中调用受限的average 函数。
  • @dyp:代码已经确实接受 const 或非 const 迭代器。问题是如何传达这个事实。

标签: c++ templates c++11 c++-concepts


【解决方案1】:
template <class ConstIt>

就这么简单。在调用方这里没有什么需要强制执行的,因为非const 迭代器也可用于const 访问,所以它只是 API 文档,这就是您选择的参数标识符 - API 文档。

这确实导致了被调用者/函数端的强制执行问题——因此它不能假装它只会使用迭代器进行const 访问然后无论如何都要修改元素。如果您关心这一点,您可以使用一些标识符来接受参数,以明确它并不意味着在整个函数中的任何地方都使用它,然后使用更方便的标识符创建一个 const_iterator 版本。这可能很棘手,因为通常您不知道迭代器类型是否是容器的成员,更不用说该容器类型是什么以及它是否也有const_iterator,所以某种形式的概念确实是理想的 -为 C++14 祈祷。同时:

  • 让您的来电者告诉您容器类型,
  • 写下你自己的特质,或者
  • 编写一个简单的包装类,该类包含一个迭代器并确保只有const 对引用数据的访问才能脱离接口

最后一种包装方法如下图所示(并非所有迭代器 API 都已实现,因此需要根据需要充实):

template <typename Iterator>
class const_iterator
{
  public:
    typedef Iterator                                                 iterator_type;
    typedef typename std::iterator_traits<Iterator>::difference_type difference_type;
    // note: trying to add const to ...:reference or ..:pointer doesn't work,
    //       as it's like saying T* const rather than T const* aka const T*.
    typedef const typename std::iterator_traits<Iterator>::value_type& reference;
    typedef const typename std::iterator_traits<Iterator>::value_type* pointer;

    const_iterator(const Iterator& i) : i_(i) { }
    reference operator*() const { return *i_; }
    pointer operator->() const { return i_; }    
    bool operator==(const const_iterator& rhs) const { return i_ == rhs.i_; }
    bool operator!=(const const_iterator& rhs) const { return i_ != rhs.i_; }    
    const_iterator& operator++() { ++i_; return *this; }
    const_iterator operator++(int) const { Iterator i = i_; ++i_; return i; }
  private:
    Iterator i_;
};

示例用法:

template <typename Const_Iterator>
void f(const Const_Iterator& b__, const Const_Iterator& e__)
{
    const_iterator<Const_Iterator> b{b__}, e{e__}; // make a really-const iterator
    // *b = 2;  // if uncommented, compile-time error....
    for ( ; b != e; ++b)
        std::cout << *b << '\n';
}

running at ideone.com here

【讨论】:

  • 我不同意你的观点:如果必须是const,那么最好让编译器检查你的函数是否真的没有改变指针对象——否则你会发现只在函数实例化时才out,非常不方便。
  • @akappa:但在这里是不可能的。
  • @gexicide:也许你是对的(我想你可以强制使用 const 迭代器和 static_assert 和一些新的 C++11 类型查询),但我认为托尼关于有用性的论点强制执行const 有点简单。
  • value_type 不是 const,你必须使用 referencepointer
  • @Jarod42:哦 - 错过了 - 将纠正答案,谢谢。
【解决方案2】:

它需要一个迭代器并根据包含的数字计算平均值,但保证不会通过传递的迭代器修改向量。如何将这一点传达给模板的用户?

您可以在传递非常量迭代器时使用SFINAE 禁用模板,但这是不必要的限制。


另一种方法是接受ranges 而不是迭代器。这样你就可以写:

template <class Range>
typename Range::value_type average(Range const& range);

用户可以在其中传递一个容器或迭代器范围。

【讨论】:

  • @dyp 是的,auto beg = range.begin(), end = range.end() 并随心所欲地对那些迭代器做任何事情。
  • @dyp 如果迭代时范围本身在缩小,那么它也必须是可变的。 - 你是想射中自己的脚,还是射掉整条腿?
  • @dyp Range const 提供 const 迭代器(或者,学究起来,迭代器到常量元素)。
  • 抱歉,我没有看到 boost 链接。术语“范围”是模棱两可的。 “你是想打自己的脚,还是打掉整条腿?” 嗯? AFAIK,D 的范围正在“缩小”,似乎 Java 的迭代器也在迭代时修改了自己。
【解决方案3】:

您可以尝试始终通过某个函数 deref() 取消对迭代器的引用。

template <typename It>
typename ::std::remove_reference<typename ::std::iterator_traits<It>::reference>::type const&
deref(It it)
{
  return *it;
}

这将保证基础价值不会被修改。

【讨论】:

    【解决方案4】:

    您可以添加一些特征来查看迭代器是否为 const_iterator

    template <typename IT>
    using is_const_iterator = 
            std::is_const<typename std::remove_reference<typename std::iterator_traits<IT>::reference>::type>;
    

    然后使用类似的东西:

    template <typename IT>
    typename
    std::enable_if<is_const_iterator<IT>::value,
                   typename std::iterator_traits<It>::value_type
    >::type average(It begin, It end);
    

    但这将避免使用可转换为 const_iteratoriterator。 所以当 const 被禁止时最好限制 iterator (如std::sort

    【讨论】:

    • 基本上,std::vector&lt;int&gt; v{...}; auto avg = average(v.begin(), v.end() 不起作用,不是吗?你可以打电话给.cbegin(),但它真的有用吗?
    • @MaximYegorushkin 更糟糕的是,您不能在sort 中调用此版本的average,因为后者(仅)具有可变迭代器(可变对象的迭代器)。跨度>
    • @MaximYegorushkin:你已经正确理解了我的意思。这就是为什么我认为average不应该有限制,但你可以对std::sort添加限制。
    猜你喜欢
    • 2012-04-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-09
    • 2015-09-01
    • 1970-01-01
    • 2014-10-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多