如何让基于可变参数模板的std::Variant求和代码正常运行
原代码核心编译问题梳理
- 静态成员定义不合法:类内用
static auto声明的非constexpr静态变量,没有在类外补全定义;模板类内部嵌套带独立模板参数的静态变量的写法不符合C++语法规则,链接阶段会报未定义错误 - Visitor实现逻辑错误:当前的Visitor继承结构把variant的备选类型
Ts...作为要继承的函数对象基类,没有为每个备选类型生成对应的operator()重载,无法匹配std::visit的访问要求 - 函数签名匹配失败:单类型版本的
sum(T arg)需要传入T类型的实参,但主函数中调用term::legion::sum<int>()没有传入任何参数,属于实参数量不匹配 - 模板调用语法错误:调用
Calculator<Vs...>::template variant_sum<Vs...>时,Calculator的主模板参数不匹配——原Calculator是template<class T, class... Ts>的双参数列表形式,传单个Vs...参数包时缺少第一个模板参数 - 算术转换的潜在逻辑bug:两个variant取出的值直接相加时,常规整数提升规则会把char、short等类型提升为int,这个结果类型不在variant的备选列表中时,往variant塞值会触发类型不匹配的编译错误或运行时异常
修复后可编译运行的代码
#include <variant> #include <type_traits> #include <string> #include <functional> #include <iostream> #include <memory> #include <optional> #include <any> #include <utility> #include <stdexcept> /* 通用重载Visitor实现 */ template <class... Ts> struct Overloaded : Ts... { using Ts::operator()...; }; // 类模板参数推导指引,简化Visitor的实例化 template <class... Ts> Overloaded(Ts...) -> Overloaded<Ts...>; /* Calculator实现 */ // 单整型场景的特化 template<class T> struct Calculator { static constexpr auto sum = [](T a, T b) -> T { return a + b; }; }; // std::variant场景的特化 template<class... Vs> struct Calculator<std::variant<Vs...>> { static constexpr auto variant_sum = [](std::variant<Vs...> a, std::variant<Vs...> b) -> std::variant<Vs...> { auto visitor = Overloaded{ [](auto lhs, auto rhs) -> std::variant<Vs...> { auto res = lhs + rhs; // 编译期检查结果类型是否可直接存入variant if constexpr (std::is_constructible_v<std::variant<Vs...>, decltype(res)>) { return res; } else { // 兜底将提升后的整数类型转为double,适配公共类型要求 return static_cast<double>(res); } } }; // 同时对两个variant做双访问,直接处理所有存储类型的组合计算 return std::visit( [&visitor](auto&& lhs, auto&& rhs) -> std::variant<Vs...> { return visitor(lhs, rhs); }, a, b ); }; }; /* 项生成器 */ namespace term::legion { using std::function, std::any; template<typename T> function<any(any)> legion(auto&& algebra) noexcept { function<any(any)> redex = [algebra = std::forward<decltype(algebra)>(algebra)](std::any a) -> any { return a; }; return redex; } // 单类型版本的sum template<typename T> std::function<std::any(std::any)> sum() noexcept { return legion<T>(Calculator<T>::sum); }; // std::variant类型版本的sum template<typename ...Vs> std::function<std::any(std::any)> sum() noexcept { std::cout << "variant sum selected" << std::endl; return legion<std::variant<Vs...>>(Calculator<std::variant<Vs...>>::variant_sum); }; } int main() { auto sm = term::legion::sum<int>(); auto v_sm = term::legion::sum<char, int16_t, double>(); // 功能验证:直接测试variant求和逻辑 using TestVariant = std::variant<char, int16_t, double>; TestVariant a{int16_t(10)}, b{char('a')}; auto res = Calculator<TestVariant>::variant_sum(a, b); std::visit(Overloaded{ [](auto v) { std::cout << "计算结果值:" << v << ",类型:" << typeid(v).name() << std::endl; } }, res); }
关键修复逻辑说明
- 将原递归继承的Visitor替换为C++17标准常用的
Overloaded重载合并实现,通过折叠表达式展开所有基类的operator(),配合类模板参数推导指引,简化代码结构的同时避免递归继承带来的语法错误 - 将原来单模板带多参数的Calculator类改为全特化结构:针对单类型、std::variant类型分别做特化,不再在类内部嵌套带独立模板参数的静态变量,从根源解决静态成员链接、模板语法的错误
- 给原来的静态lambda加上
constexpr标记,支持类内直接初始化,不需要额外在类外补全静态成员定义 - 修正单类型sum函数的签名:移除无意义的
T arg形参,让两个无参的sum重载可以通过传入的模板参数包数量匹配——单类型调用传1个模板参数、variant调用传多个模板参数,编译期即可正确选择对应的函数重载 - 修正variant求和的访问逻辑:采用
std::visit的双参数重载形式,直接同时传入两个variant对象,不需要手动拆包单值,自动处理两个variant存储的任意类型组合的计算,配合if constexpr做编译期类型检查,保证相加结果可以正常存入variant,不会触发类型不匹配的错误
内容的提问来源于stack exchange,提问作者Sergey Inozemcev
相关产品推荐
相关产品推荐

