如何编写可以使用const迭代器的C ++ 11模板

时间:2014-07-10 11:27:35

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

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

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

#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迭代器,而是表示他们可以在这里安全使用。也就是说,我想传达我的代码所做的承诺,而不是限制来电者:&#34;我不会修改您的基础数据。&#34;

4 个答案:

答案 0 :(得分:10)

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

答案 1 :(得分:3)

您可以添加一些特征以查看迭代器是否为 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_iterator iterator 。 因此,当 const 被禁止时(例如std::sort

,最好限制 iterator

答案 2 :(得分:1)

  

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

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


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

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

用户可以在那里传递容器或迭代器范围。

答案 3 :(得分:0)

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

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

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