C++17中如何去除forward_as_tuple声明的冗余decltype?
C++17中去除序列化代码里冗余的
decltype 我手上有大量带序列化/反序列化需求的遗留代码,已完成大部分移植,目前借助模板特性实现通用序列化——将需要序列化的参数通过引用打包到std::tuple中,通用逻辑已实现,但成员变量args必须用冗余的decltype声明,无法直接使用auto:
struct foo { int32_t var1 = 0; bool var2 = false; bool var3 = false; uint32_t var4 = 0x0; decltype(std::forward_as_tuple(var1, var2, var3, var4)) args = std::forward_as_tuple(var1, var2, var3, var4); void serialize(uint8_t *out) const { SerializerWrapper::serializeTuple(out, args); } // 遗留实现仅作对比 void serializeOld(uint8_t *out) const { uint32_t offset = 0; Serializer<int32_t>::serialize(out, offset, var1); Serializer<bool>::serialize(out, offset, var2); Serializer<bool>::serialize(out, offset, var3); Serializer<uint32_t>::serialize(out, offset, var4); } // ...其他代码 };
序列化工具类实现如下:
class SerializerWrapper { public: template<typename T> static void serialize(uint8_t* data, uint32_t& offset, const T& value) { Serializer<T>::serialize(data, offset, value); // 遗留代码中已实现 } // 元组序列化逻辑 template <typename TupleT, std::size_t... Is> static void serializeTupleIx(uint8_t* data, uint32_t& offset, const TupleT& tp, std::index_sequence<Is...>) { (serialize(data, offset, std::get<Is>(tp)), ...); } template <typename TupleT, std::size_t TupSize = std::tuple_size_v<TupleT>> static void serializeTuple(uint8_t* data, const TupleT& tp) { uint32_t offset = 0; serializeTupleIx(data, offset, tp, std::make_index_sequence<TupSize>{}); } // ...其他逻辑(计算大小、反序列化等) };
补充说明:尝试过用成员函数as_tuple()返回std::tie的方式,但遇到const/非const的矛盾——serialize和size是const成员函数,需要拿到const引用的元组;而deserialize要修改成员变量,必须拿到可修改引用的元组,直接写两个重载会出现调用歧义问题:
struct foo { // a,b,c等成员变量 constexpr auto as_tuple() { return std::tie(a, b, c); } constexpr auto as_tuple() const { return std::tie(a, b, c); } void method_const() const { as_tuple(); } void method_unconst() { as_tuple(); } };
解决方案
方案1:直接使用C++17类内auto成员变量
C++17允许非静态数据成员用auto声明,只要提供类内初始化器,编译器会自动推导类型。你可以直接替换掉冗余的decltype:
struct foo { int32_t var1 = 0; bool var2 = false; bool var3 = false; uint32_t var4 = 0x0; // 直接用auto推导std::tuple<int32_t&, bool&, bool&, uint32_t&> auto args = std::forward_as_tuple(var1, var2, var3, var4); void serialize(uint8_t *out) const { SerializerWrapper::serializeTuple(out, args); } // ...其他代码 };
std::forward_as_tuple返回的是包含成员变量引用的元组,auto会正确推导这个类型,完全替代decltype的冗余写法。
方案2:正确实现as_tuple的const/非const重载
如果不想用成员变量,而是用成员函数来返回元组,可以通过引用限定符明确区分重载,避免调用歧义:
struct foo { int32_t var1 = 0; bool var2 = false; bool var3 = false; uint32_t var4 = 0x0; // 非const成员函数,仅允许非const对象调用,返回非const引用的元组 constexpr auto as_tuple() & { return std::tie(var1, var2, var3, var4); } // const成员函数,允许const/非const对象调用,返回const引用的元组 constexpr auto as_tuple() const& { return std::tie(var1, var2, var3, var4); } void serialize(uint8_t *out) const { // 自动匹配const&版本,拿到const引用的元组 SerializerWrapper::serializeTuple(out, as_tuple()); } void deserialize(const uint8_t *in) { // 自动匹配&版本,拿到可修改引用的元组 SerializerWrapper::deserializeTuple(in, as_tuple()); } };
通过&和const&引用限定符,编译器会根据调用上下文自动匹配正确的重载:const成员函数内调用时,匹配const&版本;非const成员函数内调用时,匹配&版本,彻底解决歧义问题。
内容的提问来源于stack exchange,提问作者user3895986
相关产品推荐
相关产品推荐

