Причина, по которой вышеприведенный пример не работает, заключается в том, что 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
. Это бросает в этом примере. В качестве альтернативы он может вернуть std::monostate
, если он содержится в цели variant
.
С этими определениями std::visit
будет вызывать conversion_visitor::operator()
. Если переменная, хранящаяся в источнике, имеет тип, который может обрабатывать цель, этот вызов перенаправляется на gen_variant::operator()
. В противном случае он передается на not_gen_variant::operator()
. gen_variant::operator()
просто вызывает конструктор цели variant
с исходным элементом в качестве аргумента.
Осталось описать, как получить common_types
и single_types
, используя struct split_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
s 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...>
) за другим и проверяет, если цель также 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';
}