如何在C++17编译期检查函数映射的键唯一性与结构体字段数匹配
在C++17中解决constexpr映射包含lambda/函数指针的编译错误问题
问题根源
你遇到的编译错误是因为std::function不是C++17标准中的字面类型(literal type),而constexpr变量的类型必须满足字面类型要求,因此无法将std::pair<long long, std::function<void(int)>>数组声明为constexpr。
解决方案(C++17兼容)
核心思路是使用字面类型的可调用对象替代std::function,同时保留编译期的字段数量和重复键检查逻辑。最直接的方案是用无捕获lambda转换为函数指针——无捕获lambda可以隐式转换为对应的函数指针类型,而函数指针属于字面类型,能放入constexpr数组。
修改后的完整代码示例
Header.hpp 调整
#include <map> #include <string> #include <tuple> #include <type_traits> struct Container { int fieldA; std::string fieldB; bool fieldC; }; constexpr inline long long int hash(char const *str, int h = 0) { return (!str[h] ? 5381 : (hash(str, h + 1) * 7) ^ str[h]); } template <typename T, typename V> constexpr bool hasDuplicates(const std::pair<T, V> *array, std::size_t size) { for (std::size_t i = 1; i < size; i++) { for (std::size_t j = 0; j < i; j++) { if (array[i].first == array[j].first) { return true; } } } return false; } // 结构体字段数量检测逻辑(保持原代码不变) template <typename T, typename... TArgs> auto isAggregateConstructableImpl(std::tuple<TArgs...>) -> decltype(T{std::declval<TArgs>()...}); template <typename T, typename TArgs, typename = void> struct isAggregateConstructable : std::false_type { }; template <typename T, typename TArgs> struct isAggregateConstructable<T, TArgs, std::void_t<decltype(isAggregateConstructableImpl<T>(std::declval<TArgs>()))>> : std::true_type { }; template <typename T, typename TArgs> constexpr auto isAggregateConstructableV = isAggregateConstructable<T, TArgs>::value; class any { public: template <typename T> operator T() const; }; template <class T, typename... TArgs> constexpr std::size_t numBindingsImpl() { if constexpr (isAggregateConstructable<T, std::tuple<TArgs...>>()) { return numBindingsImpl<T, any, TArgs...>(); } else { return sizeof...(TArgs) - 1; } }; template <typename T> constexpr auto structGetNumberOfFields = numBindingsImpl<T, any>(); class Holder { public: static std::map<long long, long> MakeMapString(); // 新增函数指针类型的映射创建函数 static std::map<long long, void(*)(int)> MakeMapFunction(); static const std::map<long long, long> list; };
source.cpp 实现函数指针版本的映射
#include "Header.hpp" #include <map> std::map<long long, long> Holder::MakeMapString() { constexpr std::pair<long long, long> ini[]{ {hash("fieldA"), 55}, {hash("fieldB"), 77}, {hash("fieldC"), 99}}; static_assert(end(ini) - begin(ini) == structGetNumberOfFields<Container>, "[Container] 字段数量不匹配"); static_assert(!hasDuplicates(ini, std::extent_v<std::remove_reference_t<decltype(ini)>>), "存在重复键"); return {std::begin(ini), std::end(ini)}; } std::map<long long, void(*)(int)> Holder::MakeMapFunction() { // 无捕获lambda自动转换为函数指针,可放入constexpr数组 constexpr std::pair<long long, void(*)(int)> ini[]{ {hash("fieldA"), [](int val) { /* 处理fieldA的业务逻辑 */ }}, {hash("fieldB"), [](int val) { /* 处理fieldB的业务逻辑 */ }}, {hash("fieldC"), [](int val) { /* 处理fieldC的业务逻辑 */ }}}; // 保留原有的编译期检查逻辑 static_assert(end(ini) - begin(ini) == structGetNumberOfFields<Container>, "[Container] 字段数量不匹配"); static_assert(!hasDuplicates(ini, std::extent_v<std::remove_reference_t<decltype(ini)>>), "存在重复键"); return {std::begin(ini), std::end(ini)}; } const std::map<long long, long> Holder::list = Holder::MakeMapString(); const std::map<long long, void(*)(int)> funcList = Holder::MakeMapFunction();
特殊情况处理:需要捕获的lambda
如果你的lambda必须捕获变量,C17中无法将其放入constexpr数组(捕获的lambda不是字面类型,且C17不支持捕获的constexpr lambda)。此时可以将编译期检查与运行期映射创建分离:
std::map<long long, std::function<void(int)>> Holder::MakeMapFunction() { // 第一步:用constexpr数组存键,完成编译期检查 constexpr long long keys[] = {hash("fieldA"), hash("fieldB"), hash("fieldC")}; static_assert(std::size(keys) == structGetNumberOfFields<Container>, "[Container] 字段数量不匹配"); // 针对纯键数组的重复检查 constexpr bool hasDupKeys() { for (std::size_t i = 1; i < std::size(keys); ++i) { for (std::size_t j = 0; j < i; ++j) { if (keys[i] == keys[j]) return true; } } return false; } static_assert(!hasDupKeys(), "存在重复键"); // 第二步:运行期创建包含捕获lambda的映射 return { {keys[0], [capturedVal](int val) { /* 使用capturedVal的逻辑 */ }}, {keys[1], [](int val) { /* 逻辑 */ }}, {keys[2], [someObj](int val) { /* 使用someObj的逻辑 */ }} }; }
这种方案需要手动保证键的顺序与映射中的键一致,但能保留编译期的字段数量和重复键检查。
内容的提问来源于stack exchange,提问作者Otto Lewis
相关产品推荐
相关产品推荐

