Skip to content

Commit

Permalink
is_variant_like
Browse files Browse the repository at this point in the history
  • Loading branch information
grisumbras committed Sep 9, 2023
1 parent 557676b commit 0e95a91
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 92 deletions.
6 changes: 6 additions & 0 deletions doc/qbk/conversion/basics.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ following list of categories. The first matching category is selected.
to the input value converted to its underlying type.]
[The result is the described enumerator, corresponding to the input
__string__.]
][
[Type satisfying __is_variant_like__]
[`std::variant` and similar types, e.g. `boost::variant2::variant`.]
[The result is equal to the result of conversion of the active variant
alternative.]
[The result holds the first alternative for which a conversion succeeds.]
][
[Type satisfying __is_optional_like__]
[]
Expand Down
1 change: 1 addition & 0 deletions doc/qbk/main.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
[def __is_sequence_like__ [link json.ref.boost__json__is_sequence_like `is_sequence_like`]]
[def __is_string_like__ [link json.ref.boost__json__is_string_like `is_string_like`]]
[def __is_tuple_like__ [link json.ref.boost__json__is_tuple_like `is_tuple_like`]]
[def __is_variant_like__ [link json.ref.boost__json__is_variant_like `is_variant_like`]]
[def __key_value_pair__ [link json.ref.boost__json__key_value_pair `key_value_pair`]]
[def __kind__ [link json.ref.boost__json__kind `kind`]]
[def __make_shared_resource__ [link json.ref.boost__json__make_shared_resource `make_shared_resource`]]
Expand Down
1 change: 1 addition & 0 deletions doc/qbk/quickref.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<member><link linkend="json.ref.boost__json__is_sequence_like">is_sequence_like</link></member>
<member><link linkend="json.ref.boost__json__is_string_like">is_string_like</link></member>
<member><link linkend="json.ref.boost__json__is_tuple_like">is_tuple_like</link></member>
<member><link linkend="json.ref.boost__json__is_variant_like">is_variant_like</link></member>
<member><link linkend="json.ref.boost__json__result_for">result_for</link></member>
<member><link linkend="json.ref.boost__json__try_value_to_tag">try_value_to_tag</link></member>
<member><link linkend="json.ref.boost__json__value_from_tag">value_from_tag</link></member>
Expand Down
28 changes: 28 additions & 0 deletions include/boost/json/conversion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,34 @@ struct is_described_class;
template<class T>
struct is_described_enum;

/** Determine if `T` should be treated as a variant
Variants are serialised the same way their active alternative is
serialised. The opposite conversion selects the first alternative for which conversion succeeds.<br>
Given `t`, a glvalue of type ` const T`, if
<tt>t.valueless_by_exception()</tt> is well-formed, then the trait provides
the member constant `value` that is equal to `true`. Otherwise, `value` is
equal to `false`.<br>
Users can specialize the trait for their own types if they don't want them
to be treated as variants. For example:
@code
namespace boost {
namespace json {
template <>
struct is_variant_like<your::variant> : std::false_type
{ };
} // namespace boost
} // namespace json
@endcode
*/
template<class T>
struct is_variant_like;

/** Determine if `T` should be treated as an optional
Optionals are serialised as `null` if empty, or as the stored type
Expand Down
60 changes: 22 additions & 38 deletions include/boost/json/detail/value_from.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,22 +152,6 @@ value_from_impl( no_conversion_tag, value&, T&&, Ctx const& )
"No suitable tag_invoke overload found for the type");
}

#ifndef BOOST_NO_CXX17_HDR_VARIANT
template< class Ctx >
struct value_from_visitor
{
value& jv;
Ctx const& ctx;

template<class T>
void
operator()(T&& t)
{
value_from( static_cast<T&&>(t), ctx, jv );
}
};
#endif // BOOST_NO_CXX17_HDR_VARIANT

template< class Ctx, class T >
struct from_described_member
{
Expand Down Expand Up @@ -243,6 +227,28 @@ value_from_impl(
jv = nullptr;
}

// variants
template< class Ctx >
struct value_from_visitor
{
value& jv;
Ctx const& ctx;

template<class T>
void
operator()(T&& t)
{
value_from( static_cast<T&&>(t), ctx, jv );
}
};

template< class Ctx, class T >
void
value_from_impl( variant_conversion_tag, value& jv, T&& from, Ctx const& ctx )
{
visit( value_from_visitor<Ctx>{ jv, ctx }, static_cast<T&&>(from) );
}

//----------------------------------------------------------
// Contextual conversions

Expand All @@ -266,28 +272,6 @@ tag_invoke(
}
#endif

#ifndef BOOST_NO_CXX17_HDR_VARIANT
// std::variant
template< class Ctx, class... Ts >
void
tag_invoke(
value_from_tag, value& jv, std::variant<Ts...>&& from, Ctx const& ctx )
{
std::visit( detail::value_from_visitor<Ctx>{ jv, ctx }, std::move(from) );
}

template< class Ctx, class... Ts >
void
tag_invoke(
value_from_tag,
value& jv,
std::variant<Ts...> const& from,
Ctx const& ctx )
{
std::visit( detail::value_from_visitor<Ctx>{ jv, ctx }, from );
}
#endif // BOOST_NO_CXX17_HDR_VARIANT

} // namespace json
} // namespace boost

Expand Down
107 changes: 78 additions & 29 deletions include/boost/json/detail/value_to.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,84 @@ value_to_impl(
return try_value_to<Inner>(jv, ctx);
}

// variants
template< class T, class V, class I >
using variant_construction_category = mp11::mp_cond<
std::is_constructible< T, variant2::in_place_index_t<I::value>, V >,
mp11::mp_int<2>,
#ifndef BOOST_NO_CXX17_HDR_VARIANT
std::is_constructible< T, std::in_place_index_t<I::value>, V >,
mp11::mp_int<1>,
#endif // BOOST_NO_CXX17_HDR_VARIANT
mp11::mp_true,
mp11::mp_int<0> >;

template< class T, class I, class V >
T
initialize_variant( V&& v, mp11::mp_int<0> )
{
T t;
t.template emplace<I::value>( std::move(v) );
return t;
}

template< class T, class I, class V >
T
initialize_variant( V&& v, mp11::mp_int<2> )
{
return T( variant2::in_place_index_t<I::value>(), std::move(v) );
}

#ifndef BOOST_NO_CXX17_HDR_VARIANT
template< class T, class I, class V >
T
initialize_variant( V&& v, mp11::mp_int<1> )
{
return T( std::in_place_index_t<I::value>(), std::move(v) );
}
#endif // BOOST_NO_CXX17_HDR_VARIANT

template< class T, class Ctx >
struct alternative_converter
{
result<T>& res;
value const& jv;
Ctx const& ctx;

template< class I >
void operator()( I ) const
{
if( res )
return;

using V = mp11::mp_at<T, I>;
auto attempt = try_value_to<V>(jv, ctx);
if( attempt )
{
using cat = variant_construction_category<T, V, I>;
res = initialize_variant<T, I>( std::move(*attempt), cat() );
}
}
};

template< class T, class Ctx >
result<T>
value_to_impl(
variant_conversion_tag,
try_value_to_tag<T>,
value const& jv,
Ctx const& ctx)
{
error_code ec;
BOOST_JSON_FAIL(ec, error::exhausted_variants);

using Is = mp11::mp_iota< mp11::mp_size<T> >;

result<T> res = {system::in_place_error, ec};
mp11::mp_for_each<Is>( alternative_converter<T, Ctx>{res, jv, ctx} );
return res;
}

//----------------------------------------------------------
// User-provided conversions; throwing -> throwing
template< class T, class Ctx >
Expand Down Expand Up @@ -841,35 +919,6 @@ tag_invoke(
}
#endif

// std::variant
#ifndef BOOST_NO_CXX17_HDR_VARIANT
template< class... Ts, class Ctx1, class Ctx2 >
result< std::variant<Ts...> >
tag_invoke(
try_value_to_tag< std::variant<Ts...> >,
value const& jv,
Ctx1 const&,
Ctx2 const& ctx)
{
error_code ec;
BOOST_JSON_FAIL(ec, error::exhausted_variants);

using Variant = std::variant<Ts...>;
result<Variant> res = {system::in_place_error, ec};
mp11::mp_for_each< mp11::mp_iota_c<sizeof...(Ts)> >([&](auto I) {
if( res )
return;

using T = std::variant_alternative_t<I.value, Variant>;
auto attempt = try_value_to<T>(jv, ctx);
if( attempt)
res.emplace(std::in_place_index_t<I>(), std::move(*attempt));
});

return res;
}
#endif // BOOST_NO_CXX17_HDR_VARIANT

} // namespace json
} // namespace boost

Expand Down
74 changes: 49 additions & 25 deletions include/boost/json/impl/conversion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ struct sequence_conversion_tag { };
struct tuple_conversion_tag { };
struct described_class_conversion_tag { };
struct described_enum_conversion_tag { };
struct variant_conversion_tag { };
struct optional_conversion_tag { };
struct no_conversion_tag { };

Expand Down Expand Up @@ -217,40 +218,55 @@ template< class T >
using described_bases = describe::describe_bases<
T, describe::mod_any_access>;

template< class T >
using library_conversion_category = mp11::mp_cond<
// native conversions (constructors and member functions of value)
std::is_same<T, value>, value_conversion_tag,
std::is_same<T, array>, array_conversion_tag,
std::is_same<T, object>, object_conversion_tag,
std::is_same<T, string>, string_conversion_tag,
std::is_same<T, bool>, bool_conversion_tag,
std::is_arithmetic<T>, number_conversion_tag,
// generic conversions
is_null_like<T>, null_like_conversion_tag,
is_string_like<T>, string_like_conversion_tag,
is_map_like<T>, map_like_conversion_tag,
is_sequence_like<T>, sequence_conversion_tag,
is_tuple_like<T>, tuple_conversion_tag,
is_described_class<T>, described_class_conversion_tag,
is_described_enum<T>, described_enum_conversion_tag,
is_optional_like<T>, optional_conversion_tag,
// failed to find a suitable implementation
mp11::mp_true, no_conversion_tag>;

// user conversion (via tag_invoke)
template< class Ctx, class T, class Dir >
using user_conversion_category = mp11::mp_cond<
// user conversion (via tag_invoke)
has_user_conversion3<Ctx, T, Dir>, full_context_conversion_tag,
has_user_conversion2<Ctx, T, Dir>, context_conversion_tag,
has_user_conversion1<T, Dir>, user_conversion_tag>;

// native conversions (constructors and member functions of value)
template< class T >
using native_conversion_category = mp11::mp_cond<
std::is_same<T, value>, value_conversion_tag,
std::is_same<T, array>, array_conversion_tag,
std::is_same<T, object>, object_conversion_tag,
std::is_same<T, string>, string_conversion_tag>;

// generic conversions
template< class T >
using generic_conversion_category = mp11::mp_cond<
std::is_same<T, bool>, bool_conversion_tag,
std::is_arithmetic<T>, number_conversion_tag,
is_null_like<T>, null_like_conversion_tag,
is_string_like<T>, string_like_conversion_tag,
is_map_like<T>, map_like_conversion_tag,
is_sequence_like<T>, sequence_conversion_tag,
is_tuple_like<T>, tuple_conversion_tag,
is_described_class<T>, described_class_conversion_tag,
is_described_enum<T>, described_enum_conversion_tag,
is_variant_like<T>, variant_conversion_tag,
is_optional_like<T>, optional_conversion_tag,
// failed to find a suitable implementation
mp11::mp_true, no_conversion_tag>;

template< class T >
using nested_type = typename T::type;
template< class T1, class T2 >
using conversion_category_impl_helper = mp11::mp_eval_if_not<
std::is_same<detail::no_conversion_tag, T1>,
T1,
mp11::mp_eval_or_q, T1, mp11::mp_quote<nested_type>, T2>;
template< class Ctx, class T, class Dir >
struct conversion_category_impl
{
using type = mp11::mp_eval_or<
library_conversion_category<T>,
user_conversion_category, Ctx, T, Dir>;
using type = mp11::mp_fold<
mp11::mp_list<
mp11::mp_defer<user_conversion_category, Ctx, T, Dir>,
mp11::mp_defer<native_conversion_category, T>,
mp11::mp_defer<generic_conversion_category, T>>,
no_conversion_tag,
conversion_category_impl_helper>;
};
template< class Ctx, class T, class Dir >
using conversion_category =
Expand Down Expand Up @@ -379,6 +395,10 @@ using value_result_type = typename std::decay<
template< class T >
using can_reset = decltype( std::declval<T&>().reset() );

template< class T >
using has_valueless_by_exception =
decltype( std::declval<T const&>().valueless_by_exception() );

} // namespace detail

template <class T>
Expand Down Expand Up @@ -442,6 +462,10 @@ struct is_described_enum
: describe::has_describe_enumerators<T>
{ };

template<class T>
struct is_variant_like : mp11::mp_valid<detail::has_valueless_by_exception, T>
{ };

template<class T>
struct is_optional_like
: mp11::mp_and<
Expand Down
Loading

0 comments on commit 0e95a91

Please sign in to comment.