C++如何实现双序列与值对序列迭代计算的代码复用?
问题描述
现有一个计算两个数值序列的函数:
template<typename T> inline constexpr bool is_non_bool_arithmetic = std::is_arithmetic_v<T> && !std::is_same_v<T,bool>; template<typename IT, typename = std::enable_if_t<is_non_bool_arithmetic<typename std::iterator_traits<IT>::value_type>>> double calc(IT xbegin, IT xend, IT ybegin, IT yend) { while((xbegin < xend) && (ybegin < yend)) { // 使用*xbegin和*ybegin计算逻辑,需保留并使用中间值,因此不适合每次循环调用独立函数 ++xbegin; ++ybegin; } return something; }
现在需要实现一个接收值对序列的版本:
template<typename IT, /* 暂时忽略类型检查 */> double calc(IT xybegin, IT xyend) { while(xybegin < xyend) { // 使用std::get<0>(*xybegin)和std::get<1>(*xybegin)执行计算逻辑 ++xybegin; } return something; }
请问:是否存在通用方式实现两个版本的代码复用(让其中一个版本调用另一个)且不复制数据?比如能否将值对序列转换为对应第一个/第二个值的迭代器,或者反过来?C++17及更高版本是否支持这种实现?
解决方案
完全可以实现无复制的代码复用,C++17及以上版本提供了足够的特性来支持这种需求,核心思路是包装迭代器,让它适配另一个版本的接口,不需要复制任何数据。
方案1:让值对版本调用双序列版本
我们可以实现两个“投影迭代器”,分别从值对迭代器中提取第一个和第二个元素,然后把这两个投影迭代器传给双序列版本的calc函数。
首先实现投影迭代器模板:
template<typename IT, size_t Index> struct ProjIterator { IT it; using value_type = std::decay_t<decltype(std::get<Index>(*it))>; using reference = decltype(std::get<Index>(*it)); using pointer = void; using difference_type = typename std::iterator_traits<IT>::difference_type; using iterator_category = typename std::iterator_traits<IT>::iterator_category; reference operator*() const { return std::get<Index>(*it); } ProjIterator& operator++() { ++it; return *this; } ProjIterator operator++(int) { auto tmp = *this; ++it; return tmp; } bool operator!=(const ProjIterator& other) const { return it != other.it; } bool operator<(const ProjIterator& other) const { return it < other.it; } // 可根据原迭代器类型补充--、+=等操作符 }; // 辅助函数,简化投影迭代器的创建 template<size_t Index, typename IT> ProjIterator<IT, Index> make_proj_iterator(IT it) { return {it}; }
然后修改值对版本的calc函数,调用双序列版本:
template<typename IT> double calc(IT xybegin, IT xyend) { static_assert(is_non_bool_arithmetic<std::decay_t<decltype(std::get<0>(*xybegin))>>, "值对的第一个元素必须是非布尔类型的算术类型"); static_assert(is_non_bool_arithmetic<std::decay_t<decltype(std::get<1>(*xybegin))>>, "值对的第二个元素必须是非布尔类型的算术类型"); return calc(make_proj_iterator<0>(xybegin), make_proj_iterator<0>(xyend), make_proj_iterator<1>(xybegin), make_proj_iterator<1>(xyend)); }
方案2:让双序列版本调用值对版本
反过来,我们可以把两个独立序列的迭代器包装成一个“值对迭代器”,每次解引用返回两个迭代器指向元素的std::pair,然后传给值对版本的calc。
实现值对包装迭代器:
template<typename ITX, typename ITY> struct PairIterator { ITX xit; ITY yit; using value_type = std::pair<std::decay_t<decltype(*xit)>, std::decay_t<decltype(*yit)>>; using reference = std::pair<decltype(*xit), decltype(*yit)>; using difference_type = std::common_type_t<typename std::iterator_traits<ITX>::difference_type, typename std::iterator_traits<ITY>::difference_type>; using iterator_category = std::input_iterator_tag; // 取两个迭代器中最基础的分类 reference operator*() const { return {*xit, *yit}; } PairIterator& operator++() { ++xit; ++yit; return *this; } PairIterator operator++(int) { auto tmp = *this; ++xit; ++yit; return tmp; } bool operator!=(const PairIterator& other) const { return xit != other.xit && yit != other.yit; } bool operator<(const PairIterator& other) const { return xit < other.xit && yit < other.yit; } }; // 辅助创建函数 template<typename ITX, typename ITY> PairIterator<ITX, ITY> make_pair_iterator(ITX xit, ITY yit) { return {xit, yit}; }
修改双序列版本的calc函数:
template<typename IT, typename = std::enable_if_t<is_non_bool_arithmetic<typename std::iterator_traits<IT>::value_type>>> double calc(IT xbegin, IT xend, IT ybegin, IT yend) { auto begin = make_pair_iterator(xbegin, ybegin); auto end = make_pair_iterator(xend, yend); return calc(begin, end); }
关键注意点
- 迭代器兼容性:包装后的迭代器要尽量匹配原迭代器的分类(比如原迭代器是随机访问型,就补充对应的
operator[]、operator+=等操作),确保能无缝适配原calc函数的循环逻辑。 - 类型约束:两个版本都要保留类型检查(
static_assert或enable_if),避免传入不符合要求的迭代器。 - 性能无损耗:这种迭代器包装是编译期实现的,编译器会完全优化掉包装层的额外操作,不会带来运行时开销。
内容的提问来源于stack exchange,提问作者ardabro
相关产品推荐
相关产品推荐

