From a07e38be41813d698091e7abaa3bea76dc945c63 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Sun, 30 Jul 2023 15:40:34 +0300 Subject: [PATCH] is_variant_like --- doc/qbk/conversion/basics.qbk | 6 ++ doc/qbk/main.qbk | 1 + doc/qbk/quickref.xml | 1 + include/boost/json/conversion.hpp | 29 ++++++ include/boost/json/detail/value_from.hpp | 60 +++++-------- include/boost/json/detail/value_to.hpp | 107 +++++++++++++++++------ include/boost/json/impl/conversion.hpp | 74 ++++++++++------ test/value_to.cpp | 23 +++++ 8 files changed, 209 insertions(+), 92 deletions(-) diff --git a/doc/qbk/conversion/basics.qbk b/doc/qbk/conversion/basics.qbk index 999851b6d..4e3c5726c 100644 --- a/doc/qbk/conversion/basics.qbk +++ b/doc/qbk/conversion/basics.qbk @@ -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__] [] diff --git a/doc/qbk/main.qbk b/doc/qbk/main.qbk index a97fdc9ec..6de680329 100644 --- a/doc/qbk/main.qbk +++ b/doc/qbk/main.qbk @@ -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`]] diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index a31d70fef..1fff95fdf 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -73,6 +73,7 @@ is_sequence_like is_string_like is_tuple_like + is_variant_like result_for try_value_to_tag value_from_tag diff --git a/include/boost/json/conversion.hpp b/include/boost/json/conversion.hpp index 799e0cae0..96a3a14db 100644 --- a/include/boost/json/conversion.hpp +++ b/include/boost/json/conversion.hpp @@ -331,6 +331,35 @@ struct is_described_class; template 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.
+ + Given `t`, a glvalue of type ` const T`, if + t.valueless_by_exception() is well-formed, then the trait provides + the member constant `value` that is equal to `true`. Otherwise, `value` is + equal to `false`.
+ + 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 : std::false_type + { }; + + } // namespace boost + } // namespace json + @endcode +*/ +template +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 diff --git a/include/boost/json/detail/value_from.hpp b/include/boost/json/detail/value_from.hpp index 9c455c44e..27e06f3a8 100644 --- a/include/boost/json/detail/value_from.hpp +++ b/include/boost/json/detail/value_from.hpp @@ -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 - void - operator()(T&& t) - { - value_from( static_cast(t), ctx, jv ); - } -}; -#endif // BOOST_NO_CXX17_HDR_VARIANT - template< class Ctx, class T > struct from_described_member { @@ -243,6 +227,28 @@ value_from_impl( jv = nullptr; } +// variants +template< class Ctx > +struct value_from_visitor +{ + value& jv; + Ctx const& ctx; + + template + void + operator()(T&& t) + { + value_from( static_cast(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{ jv, ctx }, static_cast(from) ); +} + //---------------------------------------------------------- // Contextual conversions @@ -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&& from, Ctx const& ctx ) -{ - std::visit( detail::value_from_visitor{ jv, ctx }, std::move(from) ); -} - -template< class Ctx, class... Ts > -void -tag_invoke( - value_from_tag, - value& jv, - std::variant const& from, - Ctx const& ctx ) -{ - std::visit( detail::value_from_visitor{ jv, ctx }, from ); -} -#endif // BOOST_NO_CXX17_HDR_VARIANT - } // namespace json } // namespace boost diff --git a/include/boost/json/detail/value_to.hpp b/include/boost/json/detail/value_to.hpp index 504e364b0..7bd6b2240 100644 --- a/include/boost/json/detail/value_to.hpp +++ b/include/boost/json/detail/value_to.hpp @@ -521,6 +521,84 @@ value_to_impl( return try_value_to(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, V >, + mp11::mp_int<2>, +#ifndef BOOST_NO_CXX17_HDR_VARIANT + std::is_constructible< T, std::in_place_index_t, 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( 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(), 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(), std::move(v) ); +} +#endif // BOOST_NO_CXX17_HDR_VARIANT + +template< class T, class Ctx > +struct alternative_converter +{ + result& res; + value const& jv; + Ctx const& ctx; + + template< class I > + void operator()( I ) const + { + if( res ) + return; + + using V = mp11::mp_at; + auto attempt = try_value_to(jv, ctx); + if( attempt ) + { + using cat = variant_construction_category; + res = initialize_variant( std::move(*attempt), cat() ); + } + } +}; + +template< class T, class Ctx > +result +value_to_impl( + variant_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ctx) +{ + error_code ec; + BOOST_JSON_FAIL(ec, error::exhausted_variants); + + using Is = mp11::mp_iota< mp11::mp_size >; + + result res = {system::in_place_error, ec}; + mp11::mp_for_each( alternative_converter{res, jv, ctx} ); + return res; +} + //---------------------------------------------------------- // User-provided conversions; throwing -> throwing template< class T, class Ctx > @@ -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 > -tag_invoke( - try_value_to_tag< std::variant >, - value const& jv, - Ctx1 const&, - Ctx2 const& ctx) -{ - error_code ec; - BOOST_JSON_FAIL(ec, error::exhausted_variants); - - using Variant = std::variant; - result res = {system::in_place_error, ec}; - mp11::mp_for_each< mp11::mp_iota_c >([&](auto I) { - if( res ) - return; - - using T = std::variant_alternative_t; - auto attempt = try_value_to(jv, ctx); - if( attempt) - res.emplace(std::in_place_index_t(), std::move(*attempt)); - }); - - return res; -} -#endif // BOOST_NO_CXX17_HDR_VARIANT - } // namespace json } // namespace boost diff --git a/include/boost/json/impl/conversion.hpp b/include/boost/json/impl/conversion.hpp index f2047d846..7e327c8db 100644 --- a/include/boost/json/impl/conversion.hpp +++ b/include/boost/json/impl/conversion.hpp @@ -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 { }; @@ -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, value_conversion_tag, - std::is_same, array_conversion_tag, - std::is_same, object_conversion_tag, - std::is_same, string_conversion_tag, - std::is_same, bool_conversion_tag, - std::is_arithmetic, number_conversion_tag, - // generic conversions - is_null_like, null_like_conversion_tag, - is_string_like, string_like_conversion_tag, - is_map_like, map_like_conversion_tag, - is_sequence_like, sequence_conversion_tag, - is_tuple_like, tuple_conversion_tag, - is_described_class, described_class_conversion_tag, - is_described_enum, described_enum_conversion_tag, - is_optional_like, 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, full_context_conversion_tag, has_user_conversion2, context_conversion_tag, has_user_conversion1, user_conversion_tag>; +// native conversions (constructors and member functions of value) +template< class T > +using native_conversion_category = mp11::mp_cond< + std::is_same, value_conversion_tag, + std::is_same, array_conversion_tag, + std::is_same, object_conversion_tag, + std::is_same, string_conversion_tag>; + +// generic conversions +template< class T > +using generic_conversion_category = mp11::mp_cond< + std::is_same, bool_conversion_tag, + std::is_arithmetic, number_conversion_tag, + is_null_like, null_like_conversion_tag, + is_string_like, string_like_conversion_tag, + is_map_like, map_like_conversion_tag, + is_sequence_like, sequence_conversion_tag, + is_tuple_like, tuple_conversion_tag, + is_described_class, described_class_conversion_tag, + is_described_enum, described_enum_conversion_tag, + is_variant_like, variant_conversion_tag, + is_optional_like, 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, + T1, + mp11::mp_eval_or_q, T1, mp11::mp_quote, T2>; template< class Ctx, class T, class Dir > struct conversion_category_impl { - using type = mp11::mp_eval_or< - library_conversion_category, - user_conversion_category, Ctx, T, Dir>; + using type = mp11::mp_fold< + mp11::mp_list< + mp11::mp_defer, + mp11::mp_defer, + mp11::mp_defer>, + no_conversion_tag, + conversion_category_impl_helper>; }; template< class Ctx, class T, class Dir > using conversion_category = @@ -379,6 +395,10 @@ using value_result_type = typename std::decay< template< class T > using can_reset = decltype( std::declval().reset() ); +template< class T > +using has_valueless_by_exception = + decltype( std::declval().valueless_by_exception() ); + } // namespace detail template @@ -442,6 +462,10 @@ struct is_described_enum : describe::has_describe_enumerators { }; +template +struct is_variant_like : mp11::mp_valid +{ }; + template struct is_optional_like : mp11::mp_and< diff --git a/test/value_to.cpp b/test/value_to.cpp index 4b94609df..1d0db4ec3 100644 --- a/test/value_to.cpp +++ b/test/value_to.cpp @@ -165,6 +165,23 @@ tag_invoke( return make_error_code(boost::json::error::syntax); } +// not default-constructible +struct T11 +{ + int n; + + explicit T11( int v ) + : n(v) + {} +}; +T11 +tag_invoke( + boost::json::value_to_tag, + boost::json::value const& jv) +{ + return T11( jv.to_number() ); +} + } // namespace value_to_test_ns namespace std @@ -506,6 +523,12 @@ class value_to_test value_to( value(), ctx... ); BOOST_TEST_THROWS_WITH_LOCATION( value_to( jv, ctx... )); + + jv = 1024; + using VT11 = Variant< value_to_test_ns::T11 >; + VT11 v11 = value_to< VT11 >( jv, ctx... ); + BOOST_TEST( v11.index() == 0 ); + BOOST_TEST( get<0>(v11).n == 1024 ); } template< class... Context >