如何正确将boost::variant替换为std::variant?
首先,你的问题和std::variant没有recursive_variant特性完全无关——你当前的场景根本没用到递归变体(比如包含自身类型的variant),所以这个特性缺失不影响替换。真正的问题是std::variant对重复类型的构造规则和boost::variant不同,导致编译失败。
错误原因分析
当你的tuple里包含重复类型(比如两个double)时,直接返回std::get<n>(tpl)去构造std::variant<T...>会触发歧义:
- boost::variant允许重复类型,并且会自动选择第一个匹配的类型完成构造;
- 而std::variant的模板构造函数要求传入的参数类型在variant的类型列表中是唯一可匹配的,否则编译器无法确定要初始化variant的哪个类型分支,导致SFINAE失败(就是你看到的
__find_unambiguous_index_sfinae相关错误)。
另外,std::get<n>(tpl)返回的是const引用,这也加剧了类型推导的问题。
修复方案
最直接的解决办法是显式指定要构造的std::variant的类型索引,用std::in_place_index<n>来明确告诉编译器要初始化variant的第n个类型分支。修改_tuple_index函数中的返回语句即可:
修复后的完整代码
#include <tuple> #include <iostream> #include <variant> #include <utility> // 引入std::in_place_index template <size_t n, typename... T> std::variant<T...> _tuple_index(size_t i, const std::tuple<T...>& tpl) { if (i == n) // 显式指定构造variant的第n个类型分支 return std::variant<T...>(std::in_place_index<n>, std::get<n>(tpl)); else if (n == sizeof...(T) - 1) throw std::out_of_range("Out of Index"); else return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(i, tpl); } template <typename... T> std::variant<T...> tuple_index(size_t i, const std::tuple<T...>& tpl) { return _tuple_index<0>(i, tpl); } template <typename T> auto tuple_len(T &tpl) { return std::tuple_size<T>::value; } // std::variant的流输出辅助函数 template <typename T0, typename ... Ts> std::ostream & operator<< (std::ostream & s, std::variant<T0, Ts...> const & v) { std::visit([&](auto && arg){ s << arg;}, v); return s; } int main() { std::tuple<std::string, double, double, int> t("123", 4.5, 6.7, 8); for(int i = 0; i != tuple_len(t); ++i) { std::cout << tuple_index(i, t) << std::endl; } }
另一种简化修复
如果你不想引入std::in_place_index,也可以通过显式构造std::variant的方式解决(本质是让编译器明确类型匹配):
// 将原来的return std::get<n>(tpl);替换为 using VariantType = std::variant<T...>; return VariantType(std::get<n>(tpl));
不过这种方式在有重复类型时,虽然当前场景下因为std::get<n>(tpl)的类型和variant第n个类型完全一致,编译器可以正确匹配,但std::in_place_index的方式更清晰、更安全,尤其在复杂场景下。
关于recursive_variant的补充
std::variant确实没有boost::variant的recursive_variant特性,但如果你的代码需要递归变体(比如定义一个包含自身的variant类型,用于解析AST等场景),可以通过std::unique_ptr或std::shared_ptr间接实现,比如:
struct Node; using VariantType = std::variant<int, std::unique_ptr<Node>>; struct Node { VariantType value; };
这完全可以替代boost的recursive_variant功能,所以即使需要递归场景,也依然可以用std::variant替换boost::variant。
内容的提问来源于stack exchange,提问作者Changkun

