在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;
答案 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';
}
答案 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
)
答案 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;
}
这将保证不会修改基础价值。