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 >