18

在回答 CodeReview 上的这个问题时,我正在考虑如何编写模板函数来指示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. 我必须等待概念标准化吗?

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

4

4 回答 4

11
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';
}

在 ideone.com 上查看它的运行情况。

于 2014-07-10T11:40:52.247 回答
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_iterator的迭代器所以在禁止const时限制迭代器会更好(如)std::sort

于 2014-07-10T12:03:15.847 回答
1

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

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


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

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

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

于 2014-07-10T12:02:07.107 回答
0

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

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

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

于 2014-07-10T13:09:09.150 回答