上面的例子不起作用的原因是std::visit要求为源variant的每个类型成员重载提交的函数对象的operator()。但是对于其中一些类型,没有与目标 variant 匹配的构造函数。
解决方案是对 variants 共有的类型和仅属于源 variant 的类型的访问区别对待。
template <class To, class From>
To var2var( From && from )
{
using FRM= std::remove_reference_t<From>;
using TO= std::remove_reference_t<To>;
using common_types= typename split_types<TO, FRM>::common_types;
using single_types= typename split_types<TO, FRM>::single_types;
return std::visit(
conversion_visitor<TO, common_types, single_types>(),
std::forward<From>( from ) );
}
这里std::visit 得到一个struct conversion_visitor 的对象。后者采用模板参数common_types和single_types,其中包含源variant的类型成员按上述方式拆分。
template<class... T> struct type_list {};
template <class To, class V1, class V2>
struct conversion_visitor;
template <class To, class... CT, class... ST>
struct conversion_visitor< To, type_list<CT...>, type_list<ST...> >
: public gen_variant<To, CT>...
, public not_gen_variant<To, ST>...
{
using gen_variant<To,CT>::operator()...;
using not_gen_variant<To,ST>::operator()...;
};
type_list 是类型的容器,我们在这里使用它是因为variant 不能为空。 conversion_visitor 派生自结构 gen_variant 和 not_gen_variant,它们都重载了 operator()。
template<class To, class T>
struct gen_variant
{
To operator()( T const & elem ) { return To( elem ); }
To operator()( T && elem ) { return To( std::forward<T>( elem ) ); }
};
template<class To, class T>
struct not_gen_variant
{
To operator()( T const & ) { throw std::runtime_error("Type of element in source variant is no type member of target variant"); }
};
not_gen_variant 旨在处理 错误情况,即源包含类型变量的情况,该变量类型不是目标 variant 的成员。它在这个例子中抛出。或者,如果 std::monostate 包含在目标 variant 中,它可能会返回它。
使用这些定义std::visit 将调用conversion_visitor::operator()。如果存储在源中的变量具有目标可以处理的类型,则该调用将转发到gen_variant::operator()。否则转发到not_gen_variant::operator()。 gen_variant::operator() 只是以源元素为参数调用目标variant 的构造函数。
剩下的就是描述如何使用struct split_types获取common_types和single_types。
template<class T1, class T2>
struct split_types;
template<class... To, class... From>
struct split_types< std::variant<To...>, std::variant<From...> >
{
using to_tl= type_list<std::remove_reference_t<To>...>;
using from_tl= type_list<std::remove_reference_t<From>...>;
using common_types= typename split_types_h<to_tl, from_tl, type_list<>, type_list<> >::common_types;
using single_types= typename split_types_h<to_tl, from_tl, type_list<>, type_list<> >::single_types;
};
split_types 将目标和源variant 作为模板参数。它首先将variants 的成员放入type_lists to_tl 和from_tl。这些被转发给助手split_types_h。这里将两个空的type_lists 填充为common 和single 类型,如下所示。
template<class T1, class T2, bool>
struct append_if;
template<class... Ts, class T>
struct append_if< type_list<Ts...>, T, true >
{
using type= type_list< Ts..., T >;
};
template<class... Ts, class T>
struct append_if< type_list<Ts...>, T, false >
{
using type= type_list< Ts... >;
};
template<class T1, class T2, bool b>
using append_if_t= typename append_if<T1, T2, b>::type;
template<class T1, class T2, class CT, class ST >
struct split_types_h;
template<class... T1, class... CT, class... ST>
struct split_types_h< type_list<T1...>, type_list<>, type_list<CT...>, type_list<ST...> >
{
using common_types= type_list<CT...>;
using single_types= type_list<ST...>;
};
template<class... T1, class T2f, class... T2, class... CT, class... ST>
struct split_types_h< type_list<T1...>, type_list<T2f,T2...>, type_list<CT...>, type_list<ST...> >
{
enum : bool { contains= (std::is_same_v<T2f,T1> || ...) };
using c_types_h= append_if_t<type_list<CT...>, T2f, contains>;
using s_types_h= append_if_t<type_list<ST...>, T2f, !contains>;
using common_types= typename split_types_h<type_list<T1...>, type_list<T2...>, c_types_h, s_types_h>::common_types;
using single_types= typename split_types_h<type_list<T1...>, type_list<T2...>, c_types_h, s_types_h>::single_types;
};
split_types_h 将源 (type_list<T2f,T2...>) 的一个类型成员接在另一个之后,并检查目标是否也 contains 它。如果是这样,类型 (T2f) 将附加到 common_types(在 c_types_h 的帮助下)。否则附加到single_types。
强制转换函数可以使用如下(live demo)。
Working_Day d1= Tuesday{};
Working_Day d2= d1;
WeekDay d3= Saturday{};
d3= var2var<WeekDay>( d1 );
d2= var2var<Working_Day>( d3 );
d2= var2var<Working_Day>( d1 );
try
{
WeekDay d4= Sunday{};
d1= var2var<Working_Day>( d4 );
}
catch( std::runtime_error & err )
{
std::cerr << "Runtime error caught: " << err.what() << '\n';
}