为何这段C++代码无法编译?赋值与模板报错排查
咱们来一步步拆解并解决你遇到的两个编译问题:
问题1:无法将unsigned int转换为Node
你写的Node x = 34u;属于拷贝初始化语法,这个操作并不会调用你定义的operator=——它需要一个能直接从unsigned int创建Node对象的构造函数,而不是给已经存在的Node实例赋值。你的代码里只实现了赋值运算符,没有对应的构造函数,所以编译器找不到合法的转换路径,自然报错。
修复方案:给Node类添加一个和赋值运算符逻辑一致的模板构造函数:
template<typename T> std::enable_if_t<is_json_allowed_v<T>, Node>(const T& rhs) : m_type(typeid(T)), m_data(sizeof(T)) { std::memcpy(&m_data[0], &rhs, sizeof(T)); }
问题2:非类类型请求成员begin
这个问题出在is_json_allowed的模板定义里。虽然unsigned int满足std::is_integral_v<T>为true,但你用的GCC 7.3(Mingw-Builds 7.3.0基于此版本)在处理模板常量表达式时,不会完全短路后续的子表达式——它还是会尝试实例化element_type_t<T>,而unsigned int不是可迭代类型,调用std::begin肯定会出错。
要解决这个问题,我们需要确保element_type_t<T>只有在T是数组/可迭代类型时才会被实例化,这里提供两种方法:
方法1:给is_json_allowed添加数组特化
把数组类型的判断单独拆成模板特化,避免触发不必要的element_type_t实例化:
// 基础模板:处理非数组类型 template<typename T> struct is_json_allowed : std::integral_constant<bool, std::is_integral_v<T> || std::is_void_v<T> || std::is_same_v<T, std::string> || std::is_floating_point_v<T> > {}; // 数组类型特化 template<typename T> struct is_json_allowed<T[]> : is_json_allowed<T> {}; template<typename T, size_t N> struct is_json_allowed<T[N]> : is_json_allowed<T> {};
方法2:给element_type_t添加SFINAE保护
修改element_type_t的定义,让它只对可迭代类型生效:
template<typename T, typename = void> struct element_type {}; template<typename T> struct element_type<T, std::void_t<decltype(*std::begin(std::declval<T&>()))>> { using type = std::remove_reference_t<decltype(*std::begin(std::declval<T&>()))>; }; template<typename T> using element_type_t = typename element_type<T>::type;
这样当T不是可迭代类型时,element_type<T>没有type成员,但因为is_json_allowed里的std::is_array_v<T> && ...是逻辑与,当第一个条件为false时,编译器会跳过后面的实例化(短路特性),不会触发错误。
另外还要注意:你写的模板版operator=(const Node&)不会被编译器当成拷贝赋值运算符,应该把它改成非模板的,避免意外行为:
Node& operator=(const Node& rhs) { if (this != &rhs) { // 防止自赋值 m_type = rhs.m_type; m_data = rhs.m_data; } return *this; }
完整修复后的代码
#include <cstring> #include <typeindex> #include <type_traits> #include <iterator> #include <vector> #include <iostream> #include <string> // 带SFINAE保护的element_type_t template<typename T, typename = void> struct element_type {}; template<typename T> struct element_type<T, std::void_t<decltype(*std::begin(std::declval<T&>()))>> { using type = std::remove_reference_t<decltype(*std::begin(std::declval<T&>()))>; }; template<typename T> using element_type_t = typename element_type<T>::type; // 修复后的is_json_allowed模板 template<typename T> struct is_json_allowed : std::integral_constant<bool, std::is_integral_v<T> || std::is_void_v<T> || std::is_same_v<T, std::string> || std::is_floating_point_v<T> > {}; // 数组类型特化 template<typename T> struct is_json_allowed<T[]> : is_json_allowed<T> {}; template<typename T, size_t N> struct is_json_allowed<T[N]> : is_json_allowed<T> {}; // 可选:支持可迭代容器(如vector) template<typename T> struct is_json_allowed<T, std::void_t<element_type_t<T>>> : std::integral_constant<bool, is_json_allowed_v<element_type_t<T>> > {}; template<typename T> inline constexpr bool is_json_allowed_v = is_json_allowed<T>::value; class Node { public: Node() : m_type(typeid(void)) { } // 支持拷贝初始化的模板构造函数 template<typename T> std::enable_if_t<is_json_allowed_v<T>, Node>(const T& rhs) : m_type(typeid(T)), m_data(sizeof(T)) { std::memcpy(&m_data[0], &rhs, sizeof(T)); } // 模板赋值运算符 template<typename T> std::enable_if_t<is_json_allowed_v<T>, Node&> operator=(const T& rhs) { m_type = typeid(T); m_data.resize(sizeof(T)); std::memcpy(&m_data[0], &rhs, sizeof(T)); return *this; } // 标准拷贝赋值运算符 Node& operator=(const Node& rhs) { if (this != &rhs) { m_type = rhs.m_type; m_data = rhs.m_data; } return *this; } private: std::type_index m_type; std::vector<int8_t> m_data; }; int main() { std::cout << is_json_allowed_v<unsigned int> << std::endl; // 输出1 Node x = 34u; // 现在可以正常编译运行 return 0; }
内容的提问来源于stack exchange,提问作者D.G. Redd

