将 std::any 转换为 std::variant
Convert `std::any` to `std::variant`
在知道或怀疑其包含的类型之前,std::any
(除了存储它)没什么可做的。然后可以查询(type()
)或强制转换(any_cast
)。但是,当需要处理多种类型而不是一种类型时呢?在这种情况下,解决方案可以是将其转换为 std::variant
.
例如API 提供 std::any
个对象,但只需要一组有限的类型,并且对象需要存储在容器(向量、树等)中。
如何将std::any
转换为std::variant
?
免责声明:std::any
主要用于库代码,其目的是在一些巧妙的模板和类型擦除中替换 void *
。与任何新事物一样,std::any
可能会被过度使用和误用。如果 std::any
是您代码的正确解决方案,请三思。
此代码获取一个 std::any
对象和一个类型列表,并将该对象转换为 std::variant
,如果存储的类型不是给定类型之一,则抛出 std::bad_any_cast
。
#include <any>
#include <variant>
#include <optional>
#include <typeinfo>
template <class... Args>
auto any_to_variant_cast(std::any a) -> std::variant<Args...>
{
if (!a.has_value())
throw std::bad_any_cast();
std::optional<std::variant<Args...>> v = std::nullopt;
bool found = ((a.type() == typeid(Args) && (v = std::any_cast<Args>(std::move(a)), true)) || ...);
if (!found)
throw std::bad_any_cast{};
return std::move(*v);
}
用法示例:
auto test(const std::any& a)
{
auto v = any_to_variant_cast<int, std::string>(a);
std::visit([](auto val) { std::cout << val << std::endl; }, v);
}
一些解释:
使用 std::optional<std::variant<Args...>>
是因为 std::variant<Args...>
default constructor 构造了持有第一个备选方案的值初始化值的变体,并且要求第一个备选方案是默认可构造的。
((a.type() == typeid(Args) && (v = std::any_cast<Args>(std::move(a)), true)) || ...)
// ------------------------ -------------------------------------
// type_check any_cast
这是一个fold expression。我重命名了一些子表达式以便于解释。重命名后表达式变为:
// ((type_check && (any_cast, true)) || ...)
- 如果
type_check
是 false
那么:
由于&&
短路,(any_cast, true)
不评价
(type_check && (any_cast, true))
的计算结果为 false
- 计算折叠表达式中的下一个运算
如果 type_check
是 true
那么:
(any_cast, true)
被评估:
any_cast
被评估。变体从任何对象获取值。
any_cast
结果被丢弃
true
评价
(any_cast, true)
的计算结果为 true
(type_check && (any_cast, true))
的计算结果为 true
- 其余折叠由于
||
短路不予评价
- 整个表达式(折叠)的计算结果为
true
如果没有 type_check
计算为 true
则整个表达式(折叠)计算为 false
在知道或怀疑其包含的类型之前,std::any
(除了存储它)没什么可做的。然后可以查询(type()
)或强制转换(any_cast
)。但是,当需要处理多种类型而不是一种类型时呢?在这种情况下,解决方案可以是将其转换为 std::variant
.
例如API 提供 std::any
个对象,但只需要一组有限的类型,并且对象需要存储在容器(向量、树等)中。
如何将std::any
转换为std::variant
?
免责声明:std::any
主要用于库代码,其目的是在一些巧妙的模板和类型擦除中替换 void *
。与任何新事物一样,std::any
可能会被过度使用和误用。如果 std::any
是您代码的正确解决方案,请三思。
此代码获取一个 std::any
对象和一个类型列表,并将该对象转换为 std::variant
,如果存储的类型不是给定类型之一,则抛出 std::bad_any_cast
。
#include <any>
#include <variant>
#include <optional>
#include <typeinfo>
template <class... Args>
auto any_to_variant_cast(std::any a) -> std::variant<Args...>
{
if (!a.has_value())
throw std::bad_any_cast();
std::optional<std::variant<Args...>> v = std::nullopt;
bool found = ((a.type() == typeid(Args) && (v = std::any_cast<Args>(std::move(a)), true)) || ...);
if (!found)
throw std::bad_any_cast{};
return std::move(*v);
}
用法示例:
auto test(const std::any& a)
{
auto v = any_to_variant_cast<int, std::string>(a);
std::visit([](auto val) { std::cout << val << std::endl; }, v);
}
一些解释:
使用std::optional<std::variant<Args...>>
是因为 std::variant<Args...>
default constructor 构造了持有第一个备选方案的值初始化值的变体,并且要求第一个备选方案是默认可构造的。
((a.type() == typeid(Args) && (v = std::any_cast<Args>(std::move(a)), true)) || ...)
// ------------------------ -------------------------------------
// type_check any_cast
这是一个fold expression。我重命名了一些子表达式以便于解释。重命名后表达式变为:
// ((type_check && (any_cast, true)) || ...)
- 如果
type_check
是false
那么:-
由于
&&
短路, (any_cast, true)
不评价(type_check && (any_cast, true))
的计算结果为false
- 计算折叠表达式中的下一个运算
type_check
是 true
那么:
(any_cast, true)
被评估:any_cast
被评估。变体从任何对象获取值。any_cast
结果被丢弃true
评价(any_cast, true)
的计算结果为true
(type_check && (any_cast, true))
的计算结果为true
- 其余折叠由于
||
短路不予评价
- 整个表达式(折叠)的计算结果为
true
type_check
计算为 true
则整个表达式(折叠)计算为 false