上面的例子不起作用的原因是
std::visit
要求所提交的函数对象的
operator()
针对源
variant
的每个类型成员进行重载。但是对于其中一些类型,目标
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
不是成员类型的变量的情况。在此示例中,它会抛出异常。或者,如果目标variant
中包含std::monostate
,则可以返回该值。
使用这些定义,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_list
的to_tl
和from_tl
中。这些被转发到一个辅助函数split_types_h
。在这里,两个空的type_list
将按以下方式填充共同和单一类型。
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...>
)的每个成员,并检查目标类型是否也包含它。如果是,则使用c_types_h
的帮助将该类型(T2f
)添加到common_types
中。否则,它将被添加到single_types
中。
转换函数可以按以下方式使用(实时演示)。
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';
}