递归树形数据结构中read函数编译期类型检查失效问题分析与解决
首先我们来拆解你遇到的核心问题:你的if constexpr条件始终为false,根源在于**dataType的实际类型是引用类型,而你传入的T是值类型**——哪怕你用了remove_const_t,也没处理掉引用的差异。
疑问1:为什么条件始终为false?
看你定义dataType的代码:
using dataType = decltype(get<0>(get<0>(childs).childs));
这里的std::get(假设你用的是标准库的get)对于容器中的元素,默认返回的是左值引用类型(比如如果childs里存储的是int,get<0>返回的是int&)。所以dataType实际上是T&或者const T&,而你调用read时传入的模板参数T是值类型(比如int、std::string)。
举个例子:如果叶子节点存储的是int,那么dataType是int&,而你调用read<int>(...)时,T是int。这时候哪怕用remove_const_t,也只是去掉可能的const,但int和int&依然是完全不同的类型,导致is_same_v的结果必然是false。
你用typeid(variable).name()看到的结果一致,是因为typeid会忽略引用和cv修饰符(const/volatile),所以typeid(int).name()和typeid(const int&).name()输出是一样的,这才让你误以为类型匹配。
疑问2:如何解决这个错误?
你需要同时去掉dataType和T的引用以及cv修饰符,才能正确判断底层类型是否一致。C++20及以上可以用std::remove_cvref_t(一次性去掉const、volatile和引用),旧标准可以组合std::remove_const_t和std::remove_reference_t。
修改后的条件判断
把原来的条件替换成:
// C++20 简洁写法 if constexpr((sizeof... (residualIDs) == 0) && (is_same_v<remove_cvref_t<T>, remove_cvref_t<dataType>>)) // 兼容C++17及更早版本的写法 if constexpr((sizeof... (residualIDs) == 0) && (is_same_v<remove_const_t<remove_reference_t<T>>, remove_const_t<remove_reference_t<dataType>>>))
额外优化:修正dataType的定义(可选)
如果你希望dataType直接表示叶子节点的底层值类型,而不是引用类型,可以在定义时就去掉引用:
// 直接得到值类型,避免后续判断时重复处理引用 using dataType = std::remove_cvref_t<decltype(get<0>(get<0>(childs).childs))>;
这样后续的条件判断可以简化为:
if constexpr((sizeof... (residualIDs) == 0) && (std::is_same_v<std::remove_cvref_t<T>, dataType>))
验证修改后的代码片段
调整后的read函数核心部分大概是这样:
template<class T, class... Args> T read(const uint8_t& ID, const Args&... residualIDs){ T ret; //Generic compile-time implementation of switch-case if constexpr (sizeof... (residualIDs) > 0) { initializer_list<bool> { N::Header::guard(ID) ? (ret = get<id2idx<N::Header::ID>::getIndex()>(childs).template read<T>(residualIDs...), false) : false... }; } // 先获取底层值类型 using dataType = std::remove_cvref_t<decltype(get<0>(get<0>(childs).childs))>; if constexpr((sizeof... (residualIDs) == 0) && (std::is_same_v<std::remove_cvref_t<T>, dataType>)) { cout << "Extract" << endl; initializer_list<bool> { N::Header::guard(ID) ? ( ret = get<0>(get<id2idx<N::Header::ID>::getIndex()>(childs).childs), false) : false... }; } return move(ret); }
这样就能正确判断T和叶子节点的底层数据类型是否匹配,当类型一致时执行提取逻辑,不一致时跳过。
内容的提问来源于stack exchange,提问作者salkin

