google::protobuf::RepeatedField迭代器递增及范围检测问题
Range检测与迭代器递增问题解析
问题场景
先看两段代码的差异,第二段编译失败:
decltype(++std::declval<int*&>()) pri = {nullptr}; decltype(++(std::begin(std::declval<google::protobuf::RepeatedField<int>&>()))) nr{nullptr};
编译报错:
error: lvalue required as increment operand
但以下断言能通过,证明RepeatedField<int>的迭代器类型确实是int*:
using iterator = decltype((std::begin(std::declval<google::protobuf::RepeatedField<int>&>()))); static_assert(std::is_same_v<iterator, int*>);
核心需求是实现is_range类型 trait 检测某类型是否为range,但当前实现对RepeatedField失效:
template <typename T, typename = void> struct is_range: std::false_type {}; template <typename T> struct is_range<T, std::void_t<decltype( ++std::begin(std::declval<T&>()), std::begin(std::declval<T&>()), std::end(std::declval<T&>()), ++std::begin(std::declval<T&>()), // 问题所在 *std::begin(std::declval<T&>()) )>> : std::true_type {};
一、两行代码的类型推导差异与编译失败原因
第一行代码:
std::declval<int*&>()返回的是左值引用(int*&),前置++运算符要求操作数是左值,因此对它执行++完全合法。最终推导的类型是int*&(前置递增返回原指针的左值引用)。第二行代码:
虽然RepeatedField<int>的迭代器类型是int*,但std::begin对该类型返回的是右值int*(而非左值引用)。前置++要求操作数是可修改的左值,右值无法被修改,因此直接对std::begin的返回值执行++会触发编译错误。
二、为什么std::begin返回的int*无法递增?
static_assert只验证了迭代器的类型是int*,但没验证它的值类别(左值/右值):
RepeatedField<int>&的begin()成员函数返回的是值类型int*,而非int*&;std::begin会转发调用这个成员函数,得到的是临时的int*右值;- 标准中前置
++的操作数必须是可修改的左值,右值不满足这个要求,因此编译报错。
三、修复is_range trait的方法
原代码用++std::begin(...)作为检测条件,会因为某些容器的begin()返回右值而失效,推荐两种修复方式:
方法1:用std::next替代直接递增
std::next可以接受左值或右值迭代器,返回递增后的迭代器副本,不需要修改原迭代器,完美适配range检测的需求:
template <typename T, typename = void> struct is_range: std::false_type {}; template <typename T> struct is_range<T, std::void_t<decltype( std::next(std::begin(std::declval<T&>())), // 替换直接++操作 std::begin(std::declval<T&>()), std::end(std::declval<T&>()), *std::begin(std::declval<T&>()) )>> : std::true_type {};
方法2:强制将迭代器转为左值再递增
通过类型推导生成左值引用类型,再对其执行递增操作,兼容返回右值的begin():
template <typename T, typename = void> struct is_range: std::false_type {}; template <typename T> struct is_range<T, std::void_t<decltype( ++(std::declval<decltype(std::begin(std::declval<T&>()))&>()), // 强制转为左值引用 std::begin(std::declval<T&>()), std::end(std::declval<T&>()), *std::begin(std::declval<T&>()) )>> : std::true_type {};
内容的提问来源于stack exchange,提问作者Kamil Płani
相关产品推荐
相关产品推荐

