From b9e8b2edfe4d6f5a17ed1b3787e793e2a32dec62 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Mon, 26 Sep 2022 11:23:59 +0300 Subject: [PATCH] contextual conversions --- doc/qbk/conversion/context.qbk | 123 +++++++ doc/qbk/conversion/forward.qbk | 5 + doc/qbk/conversion/overview.qbk | 1 + doc/qbk/examples.qbk | 4 + doc/qbk/main.qbk | 1 + example/CMakeLists.txt | 8 + example/Jamfile | 2 + example/use_allocator.cpp | 89 +++++ include/boost/json/conversion.hpp | 8 + include/boost/json/detail/value_from.hpp | 128 ++++--- include/boost/json/detail/value_to.hpp | 403 ++++++++++++++++++----- include/boost/json/fwd.hpp | 10 + include/boost/json/impl/conversion.hpp | 182 +++++++--- include/boost/json/value_from.hpp | 168 +++++++++- include/boost/json/value_to.hpp | 184 ++++++++++- test/Jamfile | 5 +- test/doc_forward_conversion.cpp | 87 ++++- test/doc_types.hpp | 11 + test/snippets.cpp | 251 +++++++++++++- test/value_from.cpp | 253 ++++++++++---- test/value_to.cpp | 396 ++++++++++++++-------- 21 files changed, 1926 insertions(+), 393 deletions(-) create mode 100644 doc/qbk/conversion/context.qbk create mode 100644 example/use_allocator.cpp diff --git a/doc/qbk/conversion/context.qbk b/doc/qbk/conversion/context.qbk new file mode 100644 index 00000000..f7be8b9a --- /dev/null +++ b/doc/qbk/conversion/context.qbk @@ -0,0 +1,123 @@ +[/ + Copyright (c) 2022 Dmitry Arkhipov (grisumbras@yandex.ru) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + Official repository: https://github.com/cppalliance/json +] + +[/-----------------------------------------------------------------------------] + + +[section Contextual conversions] + +Previously in this section we've been assuming that there is a particular +fitting JSON representation for a type. But this is not always the case. Often +one needs to represent particular value with JSON of certain format in one +situation and with another format in a different situation. This can be +achieved with Boost.JSON by providing an extra argument\U00002014context. + +Let's implement conversion from `user_ns::ip_address` to a JSON string: + +[doc_context_conversion_1] + +These `tag_invoke` overloads take an extra `as_string` parameter, which +disambiguates this specific representation of `ip_address` from all other +potential representations. In order to take advantage of them one needs to pass +an `as_string` object to __value_from__ or __value_to__ as the last argument: + +[doc_context_conversion_2] + +Note, that if there is no dedicated `tag_invoke` overload for a given type and +a given context, the implementation falls back to overloads without context. +Thus it is easy to combine contextual conversions with conversions provided by +the library: + +[doc_context_conversion_3] + +[heading Conversions for third-party types] +Normally, you wouldn't be able to provide conversions for types from +third-party libraries and standard types, because it would require yout to put +`tag_invoke` overloads into namespaces you do not control. But with contexts +you can put the overloads into your namespaces. This is because the context +will add its associated namespaces into the list of namespaces where +`tag_invoke` overloads are searched. + +As an example, let's implement conversion for +[@https://en.cppreference.com/w/cpp/chrono/system_clock +`std::chrono::system_clock::time_point`s] +using [@https://en.wikipedia.org/wiki/ISO_8601 ISO 8601] format. + +[doc_context_conversion_4] + +Reverse conversion is left out for brevity. + +The new context is used in a similar fashion to `as_string` previously in this +section. + +[doc_context_conversion_5] + +One particular use case that is enabled by contexts is adaptor libraries that +define JSON conversion logic for types from a different library. + +[heading Passing data to conversion functions] +Contexts we used so far were empty classes. But contexts may have data members +and member functions just as any class. Implementers of conversion functions +can take advantage of that to have conversions configurable at runtime or pass +special objects to conversions (e.g. allocators). + +Let's rewrite conversion for `system_clock::time_point`s to allow any format +supported by `std::strftime`. + +[doc_context_conversion_6] + +This `tag_invoke` overload lets us change date conversion format at runtime. +Also note, that there is no ambiguity between `as_iso_8601` overload and +`date_format` overload. You can use both in your program: + +[doc_context_conversion_7] + +[heading Combining contexts] +Often it is needed to use several conversion contexts together. For example, +consider a log of remote users identified by IP addresses accessing a system. +We can represent it as `std::vector< +std::pair >`. We want to +serialize both `ip_address`es and `time_point`s as strings, but for this we +need both `as_string` and `as_iso_8601` contexts. To combine several contexts +just use `std::tuple`. Conversion functions will select the first element of +the tuple for which a `tag_invoke` overload exists and will call that overload. +As usual, `tag_invoke` overloads that don't use contexts and library-provided +generic conversions are also supported. Thus, here's our example: + +[doc_context_conversion_8] + +In this snippet `time_point` is converted using `tag_invoke` overload that +takes `as_iso_8601`, `ip_address` is converted using `tag_invoke` overload +that takes `as_string`, and `std::vector` is converted using a generic +conversion provided by the library. + +[heading Contexts and composite types] +As was shown previously, generic conversions provided by the library forward +contexts to conversions of nested objects. And in the case when you want to +provide your own conversion function for a composite type enabled by a +particular context, you usually also need to do that. + +Consider this example. As was discussed in a previous section, __is_map_like__ +requires that your key type satisfies __is_string_like__. Now, let's say your +keys are not string-like, but they do convert to __string__. You can make such +maps to also convert to objects using a context. But if you want to also use +another context for values, you need a way to pass the full combined context to +map elements. So, you want the following test to succeed. + +[doc_context_conversion_9] + +For this you will have to use a different overload of `tag_invoke`. This time +it has to be a function template, and it should have two parameters for +contexts. The first parameter is the specific context that disambiguates that +particular overload. The second parameter is the full context passed to +__value_to__ or __value_from__. + +[doc_context_conversion_10] + +[endsect] diff --git a/doc/qbk/conversion/forward.qbk b/doc/qbk/conversion/forward.qbk index cfa62076..4aa48ed3 100644 --- a/doc/qbk/conversion/forward.qbk +++ b/doc/qbk/conversion/forward.qbk @@ -33,4 +33,9 @@ As discussed previously, we prefer to define a non-throwing overload of __value_to__, as the latter can fallback to the former without performance degradation. +Forward declarations of contextual conversions are done very similarly: + +[doc_forward_conversion_3] +[doc_forward_conversion_4] + [endsect] diff --git a/doc/qbk/conversion/overview.qbk b/doc/qbk/conversion/overview.qbk index 72effdf8..4d58a9a2 100644 --- a/doc/qbk/conversion/overview.qbk +++ b/doc/qbk/conversion/overview.qbk @@ -21,6 +21,7 @@ from the standard library. [include custom.qbk] [include nothrow.qbk] [include alloc.qbk] +[include context.qbk] [include forward.qbk] [endsect] diff --git a/doc/qbk/examples.qbk b/doc/qbk/examples.qbk index b94d59ae..b45588b3 100644 --- a/doc/qbk/examples.qbk +++ b/doc/qbk/examples.qbk @@ -20,5 +20,9 @@ [example_validate] [endsect] +[section Allocator-aware conversion] +[example_use_allocator] +[endsect] + [endsect] diff --git a/doc/qbk/main.qbk b/doc/qbk/main.qbk index cb696e7b..bbd2eca7 100644 --- a/doc/qbk/main.qbk +++ b/doc/qbk/main.qbk @@ -115,6 +115,7 @@ [import ../../example/pretty.cpp] [import ../../example/validate.cpp] +[import ../../example/use_allocator.cpp] [import ../../include/boost/json/impl/serialize.ipp] [import ../../test/doc_background.cpp] [import ../../test/doc_parsing.cpp] diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 7fa2bb6c..5116ec77 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -49,3 +49,11 @@ add_executable(validate ) set_property(TARGET validate PROPERTY FOLDER "example") target_link_libraries(validate PRIVATE Boost::json) + +# + +add_executable(use_allocator + use_allocator.cpp +) +set_property(TARGET use_allocator PROPERTY FOLDER "example") +target_link_libraries(use_allocator PRIVATE Boost::json) diff --git a/example/Jamfile b/example/Jamfile index 41fd9efd..a63b1253 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -16,3 +16,5 @@ exe pretty : pretty.cpp ; exe proxy : proxy.cpp ; exe validate : validate.cpp ; + +exe use_allocator : use_allocator.cpp ; diff --git a/example/use_allocator.cpp b/example/use_allocator.cpp new file mode 100644 index 00000000..093f5771 --- /dev/null +++ b/example/use_allocator.cpp @@ -0,0 +1,89 @@ +// +// Copyright (c) 2023 Dmitry Arkhipov (grisumbras@yandex.ru) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/json +// + +//[example_use_allocator + +/* + This example uses a context that stores an allocator to create sequences during conversions +*/ + +#include +#include +#include +#include + +using namespace boost::json; +using namespace boost::container; + +template< class Alloc > +struct use_allocator_t +{ + Alloc allocator; +}; + +template< class Alloc > +use_allocator_t< Alloc > +use_allocator( Alloc alloc ) noexcept +{ + return { alloc }; +} + +template< + class T, + class Alloc, + class FullContext, + class = typename std::enable_if< + is_sequence_like::value && + std::uses_allocator::value + >::type > +result +tag_invoke( try_value_to_tag, const value& jv, const use_allocator_t& ctx, const FullContext& full_ctx ) +{ + + array const* arr = jv.if_array(); + if( !arr ) + return { + boost::system::in_place_error, + make_error_code(boost::system::errc::invalid_argument) }; + + T result(ctx.allocator); + auto ins = std::inserter(result, result.end()); + for( value const& val: *arr ) + { + using ValueType = typename T::value_type; + auto elem_res = try_value_to( val, full_ctx ); + if( elem_res.has_error() ) + return {boost::system::in_place_error, elem_res.error()}; + *ins++ = std::move(*elem_res); + } + return result; +} + +int +main(int, char**) +{ + + value const jv = { 1, 2, 3, 4, 5, 6, 7, 8 }; + + unsigned char buf[1024]; + pmr::monotonic_buffer_resource mr( buf, sizeof(buf) ); + + auto v = value_to< pmr::vector >( + jv, + use_allocator( pmr::polymorphic_allocator(&mr) ) ); + + assert( v.size() == jv.as_array().size() ); + + for( auto i = 0u; i < v.size(); ++i ) + assert( v[i] == jv.at(i).to_number() ); + + return EXIT_SUCCESS; +} + +//] diff --git a/include/boost/json/conversion.hpp b/include/boost/json/conversion.hpp index d0ff5494..de83d9dd 100644 --- a/include/boost/json/conversion.hpp +++ b/include/boost/json/conversion.hpp @@ -11,12 +11,20 @@ #define BOOST_JSON_CONVERSION_HPP #include +#include #include namespace boost { namespace json { +namespace detail { + +template< class Ctx, class T, class Dir > +struct supported_context; + +} // namespace detail + /** Customization point tag. This tag type is used by the function diff --git a/include/boost/json/detail/value_from.hpp b/include/boost/json/detail/value_from.hpp index 0bbebaba..abf6b041 100644 --- a/include/boost/json/detail/value_from.hpp +++ b/include/boost/json/detail/value_from.hpp @@ -25,9 +25,10 @@ namespace json { namespace detail { -template +template< class Ctx, class T > struct append_tuple_element { array& arr; + Ctx const& ctx; T&& t; template @@ -36,35 +37,52 @@ struct append_tuple_element { { using std::get; arr.emplace_back(value_from( - get(std::forward(t)), arr.storage())); + get(std::forward(t)), ctx, arr.storage() )); } }; //---------------------------------------------------------- // User-provided conversion -template +template< class T, class Ctx > void -value_from_impl( user_conversion_tag, value& jv, T&& from ) +value_from_impl( user_conversion_tag, value& jv, T&& from, Ctx const& ) { - tag_invoke(value_from_tag(), jv, std::forward(from)); + tag_invoke( value_from_tag(), jv, static_cast(from) ); } +template< class T, class Ctx > +void +value_from_impl( context_conversion_tag, value& jv, T&& from, Ctx const& ctx) +{ + using Sup = supported_context; + tag_invoke( value_from_tag(), jv, static_cast(from), Sup::get(ctx) ); +} + +template< class T, class Ctx > +void +value_from_impl( + full_context_conversion_tag, value& jv, T&& from, Ctx const& ctx) +{ + using Sup = supported_context; + tag_invoke( + value_from_tag(), jv, static_cast(from), Sup::get(ctx), ctx ); +} //---------------------------------------------------------- // Native conversion -template +template< class T, class Ctx > void -value_from_impl( native_conversion_tag, value& jv, T&& from ) +value_from_impl( native_conversion_tag, value& jv, T&& from, Ctx const& ) { jv = std::forward(from); } // null-like types -template +template< class T, class Ctx > void -value_from_impl( null_like_conversion_tag, value& jv, T&& ) +value_from_impl( null_like_conversion_tag, value& jv, T&&, Ctx const& ) { // do nothing BOOST_ASSERT(jv.is_null()); @@ -72,58 +90,62 @@ value_from_impl( null_like_conversion_tag, value& jv, T&& ) } // string-like types -template +template< class T, class Ctx > void -value_from_impl( string_like_conversion_tag, value& jv, T&& from ) +value_from_impl( string_like_conversion_tag, value& jv, T&& from, Ctx const& ) { auto sv = static_cast(from); jv.emplace_string().assign(sv); } // map-like types -template +template< class T, class Ctx > void -value_from_impl( map_like_conversion_tag, value& jv, T&& from ) +value_from_impl( map_like_conversion_tag, value& jv, T&& from, Ctx const& ctx ) { using std::get; object& obj = jv.emplace_object(); obj.reserve(detail::try_size(from, size_implementation())); for (auto&& elem : from) - obj.emplace(get<0>(elem), value_from( - get<1>(elem), obj.storage())); + obj.emplace( + get<0>(elem), + value_from( get<1>(elem), ctx, obj.storage() )); } // ranges -template +template< class T, class Ctx > void -value_from_impl( sequence_conversion_tag, value& jv, T&& from ) +value_from_impl( sequence_conversion_tag, value& jv, T&& from, Ctx const& ctx ) { array& result = jv.emplace_array(); result.reserve(detail::try_size(from, size_implementation())); + using ForwardedValue = forwarded_value; for (auto&& elem : from) result.emplace_back( value_from( - static_cast< forwarded_value >(elem), + // not a static_cast in order to appease clang < 4.0 + ForwardedValue(elem), + ctx, result.storage() )); } // tuple-like types -template +template< class T, class Ctx > void -value_from_impl( tuple_conversion_tag, value& jv, T&& from ) +value_from_impl( tuple_conversion_tag, value& jv, T&& from, Ctx const& ctx ) { constexpr std::size_t n = std::tuple_size>::value; array& arr = jv.emplace_array(); arr.reserve(n); mp11::mp_for_each>( - append_tuple_element{arr, std::forward(from)}); + append_tuple_element< Ctx, T >{ arr, ctx, std::forward(from) }); } // no suitable conversion implementation -template +template< class T, class Ctx > void -value_from_impl( no_conversion_tag, value&, T&& ) +value_from_impl( no_conversion_tag, value&, T&&, Ctx const& ) { static_assert( !std::is_same::value, @@ -131,26 +153,29 @@ value_from_impl( no_conversion_tag, value&, T&& ) } #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), jv); + value_from( static_cast(t), ctx, jv ); } }; #endif // BOOST_NO_CXX17_HDR_VARIANT -template< class T > +template< class Ctx, class T > struct from_described_member { using Ds = describe::describe_members< remove_cvref, describe::mod_public | describe::mod_inherited>; object& obj; + Ctx const& ctx; T&& from; template< class I > @@ -162,17 +187,20 @@ struct from_described_member D::name, value_from( static_cast(from).* D::pointer, + ctx, obj.storage())); } }; // described classes -template +template< class T, class Ctx > void -value_from_impl( described_class_conversion_tag, value& jv, T&& from ) +value_from_impl( + described_class_conversion_tag, value& jv, T&& from, Ctx const& ctx ) { object& obj = jv.emplace_object(); - from_described_member member_converter{obj, static_cast(from)}; + from_described_member member_converter{ + obj, ctx, static_cast(from)}; using Ds = typename decltype(member_converter)::Ds; constexpr std::size_t N = mp11::mp_size::value; @@ -181,9 +209,10 @@ value_from_impl( described_class_conversion_tag, value& jv, T&& from ) } // described enums -template +template< class T, class Ctx > void -value_from_impl( described_enum_conversion_tag, value& jv, T from ) +value_from_impl( + described_enum_conversion_tag, value& jv, T from, Ctx const& ) { (void)jv; (void)from; @@ -202,34 +231,34 @@ value_from_impl( described_enum_conversion_tag, value& jv, T from ) #endif } -template -using value_from_category = conversion_category; +//---------------------------------------------------------- +// Contextual conversions + +template< class Ctx, class T > +using value_from_category = conversion_category< + Ctx, T, value_from_conversion >; } // detail #ifndef BOOST_NO_CXX17_HDR_OPTIONAL -template +template< class T, class Ctx > void tag_invoke( - value_from_tag, - value& jv, - std::optional const& from) + value_from_tag, value& jv, std::optional const& from, Ctx const& ctx ) { if( from ) - value_from(*from, jv); + value_from( *from, ctx, jv ); else jv = nullptr; } -template +template< class T, class Ctx > void tag_invoke( - value_from_tag, - value& jv, - std::optional&& from) + value_from_tag, value& jv, std::optional&& from, Ctx const& ctx ) { if( from ) - value_from(std::move(*from), jv); + value_from( std::move(*from), ctx, jv ); else jv = nullptr; } @@ -249,24 +278,23 @@ tag_invoke( #ifndef BOOST_NO_CXX17_HDR_VARIANT // std::variant -template +template< class Ctx, class... Ts > void tag_invoke( - value_from_tag, - value& jv, - std::variant&& from) + value_from_tag, value& jv, std::variant&& from, Ctx const& ctx ) { - std::visit(detail::value_from_visitor{jv}, std::move(from)); + std::visit( detail::value_from_visitor{ jv, ctx }, std::move(from) ); } -template +template< class Ctx, class... Ts > void tag_invoke( value_from_tag, value& jv, - std::variant const& from) + std::variant const& from, + Ctx const& ctx ) { - std::visit(detail::value_from_visitor{jv}, from); + std::visit( detail::value_from_visitor{ jv, ctx }, from ); } #endif // BOOST_NO_CXX17_HDR_VARIANT diff --git a/include/boost/json/detail/value_to.hpp b/include/boost/json/detail/value_to.hpp index ec7004a2..dc2dc276 100644 --- a/include/boost/json/detail/value_to.hpp +++ b/include/boost/json/detail/value_to.hpp @@ -23,16 +23,6 @@ namespace boost { namespace json { -template::value && - std::is_same::value>::type> -T value_to(U const&); - -template -typename result_for::type -try_value_to(const value& jv); - namespace detail { template @@ -122,25 +112,33 @@ inserter( } // identity conversion -inline +template< class Ctx > result -value_to_impl( value_conversion_tag, try_value_to_tag, value const& jv ) +value_to_impl( + value_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ) { return jv; } -inline +template< class Ctx > value -value_to_impl( value_conversion_tag, value_to_tag, value const& jv ) +value_to_impl( + value_conversion_tag, value_to_tag, value const& jv, Ctx const& ) { return jv; } // object -inline +template< class Ctx > result value_to_impl( - object_conversion_tag, try_value_to_tag, value const& jv ) + object_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ) { object const* obj = jv.if_object(); if( obj ) @@ -151,10 +149,13 @@ value_to_impl( } // array -inline +template< class Ctx > result value_to_impl( - array_conversion_tag, try_value_to_tag, value const& jv ) + array_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ) { array const* arr = jv.if_array(); if( arr ) @@ -165,10 +166,13 @@ value_to_impl( } // string -inline +template< class Ctx > result value_to_impl( - string_conversion_tag, try_value_to_tag, value const& jv ) + string_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ) { string const* str = jv.if_string(); if( str ) @@ -179,9 +183,10 @@ value_to_impl( } // bool -inline +template< class Ctx > result -value_to_impl( bool_conversion_tag, try_value_to_tag, value const& jv ) +value_to_impl( + bool_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { auto b = jv.if_bool(); if( b ) @@ -192,9 +197,10 @@ value_to_impl( bool_conversion_tag, try_value_to_tag, value const& jv ) } // integral and floating point -template +template< class T, class Ctx > result -value_to_impl( number_conversion_tag, try_value_to_tag, value const& jv ) +value_to_impl( + number_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { error_code ec; auto const n = jv.to_number(ec); @@ -204,9 +210,13 @@ value_to_impl( number_conversion_tag, try_value_to_tag, value const& jv ) } // null-like conversion -template +template< class T, class Ctx > result -value_to_impl( null_like_conversion_tag, try_value_to_tag, value const& jv ) +value_to_impl( + null_like_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ) { if( jv.is_null() ) return {boost::system::in_place_value, T{}}; @@ -216,10 +226,13 @@ value_to_impl( null_like_conversion_tag, try_value_to_tag, value const& jv ) } // string-like types -template +template< class T, class Ctx > result value_to_impl( - string_like_conversion_tag, try_value_to_tag, value const& jv ) + string_like_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ) { auto str = jv.if_string(); if( str ) @@ -230,9 +243,13 @@ value_to_impl( } // map-like containers -template +template< class T, class Ctx > result -value_to_impl( map_like_conversion_tag, try_value_to_tag, value const& jv ) +value_to_impl( + map_like_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ctx ) { error_code ec; @@ -251,7 +268,7 @@ value_to_impl( map_like_conversion_tag, try_value_to_tag, value const& jv ) auto ins = detail::inserter(res, inserter_implementation()); for( key_value_pair const& kv: *obj ) { - auto elem_res = try_value_to>(kv.value()); + auto elem_res = try_value_to>( kv.value(), ctx ); if( elem_res.has_error() ) return {boost::system::in_place_error, elem_res.error()}; *ins++ = value_type{ @@ -261,9 +278,10 @@ value_to_impl( map_like_conversion_tag, try_value_to_tag, value const& jv ) return res; } -template +template< class T, class Ctx > T -value_to_impl( map_like_conversion_tag, value_to_tag, value const& jv ) +value_to_impl( + map_like_conversion_tag, value_to_tag, value const& jv, Ctx const& ctx ) { error_code ec; @@ -283,14 +301,18 @@ value_to_impl( map_like_conversion_tag, value_to_tag, value const& jv ) for( key_value_pair const& kv: *obj ) *ins++ = value_type{ key_type(kv.key()), - value_to>(kv.value())}; + value_to>( kv.value(), ctx )}; return result; } // all other containers -template +template< class T, class Ctx > result -value_to_impl( sequence_conversion_tag, try_value_to_tag, value const& jv ) +value_to_impl( + sequence_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ctx ) { error_code ec; @@ -309,7 +331,7 @@ value_to_impl( sequence_conversion_tag, try_value_to_tag, value const& jv ) auto ins = detail::inserter(result, inserter_implementation()); for( value const& val: *arr ) { - auto elem_res = try_value_to>(val); + auto elem_res = try_value_to>( val, ctx ); if( elem_res.has_error() ) return {boost::system::in_place_error, elem_res.error()}; *ins++ = std::move(*elem_res); @@ -317,9 +339,10 @@ value_to_impl( sequence_conversion_tag, try_value_to_tag, value const& jv ) return result; } -template +template< class T, class Ctx > T -value_to_impl( sequence_conversion_tag, value_to_tag, value const& jv ) +value_to_impl( + sequence_conversion_tag, value_to_tag, value const& jv, Ctx const& ctx ) { error_code ec; @@ -337,31 +360,32 @@ value_to_impl( sequence_conversion_tag, value_to_tag, value const& jv ) auto ins = detail::inserter(result, inserter_implementation()); for( value const& val: *arr ) - *ins++ = value_to>(val); + *ins++ = value_to>( val, ctx ); return result; } // tuple-like types -template +template< class T, class Ctx > result -try_make_tuple_elem(value const& jv, error_code& ec) +try_make_tuple_elem(value const& jv, Ctx const& ctx, error_code& ec) { if( ec.failed() ) return {boost::system::in_place_error, ec}; - auto result = try_value_to(jv); + auto result = try_value_to( jv, ctx ); ec = result.error(); return result; } -template +template result -try_make_tuple_like(array const& arr, boost::mp11::index_sequence) +try_make_tuple_like( + array const& arr, Ctx const& ctx, boost::mp11::index_sequence) { error_code ec; auto items = std::make_tuple( try_make_tuple_elem>( - arr[Is], ec) + arr[Is], ctx, ec) ...); if( ec.failed() ) return {boost::system::in_place_error, ec}; @@ -370,16 +394,21 @@ try_make_tuple_like(array const& arr, boost::mp11::index_sequence) boost::system::in_place_value, T(std::move(*std::get(items))...)}; } -template +template T -make_tuple_like(array const& arr, boost::mp11::index_sequence) +make_tuple_like( + array const& arr, Ctx const& ctx, boost::mp11::index_sequence) { - return T(value_to>(arr[Is])...); + return T( value_to>( arr[Is], ctx )... ); } -template +template< class T, class Ctx > result -value_to_impl( tuple_conversion_tag, try_value_to_tag, value const& jv ) +value_to_impl( + tuple_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ctx ) { error_code ec; @@ -398,12 +427,13 @@ value_to_impl( tuple_conversion_tag, try_value_to_tag, value const& jv ) } return try_make_tuple_like( - *arr, boost::mp11::make_index_sequence()); + *arr, ctx, boost::mp11::make_index_sequence()); } -template +template< class T, class Ctx > T -value_to_impl( tuple_conversion_tag, value_to_tag, value const& jv ) +value_to_impl( + tuple_conversion_tag, value_to_tag, value const& jv, Ctx const& ctx ) { error_code ec; @@ -422,7 +452,7 @@ value_to_impl( tuple_conversion_tag, value_to_tag, value const& jv ) } return make_tuple_like( - *arr, boost::mp11::make_index_sequence()); + *arr, ctx, boost::mp11::make_index_sequence()); } template< class T> @@ -437,7 +467,7 @@ struct is_optional< std::optional > { }; #endif // BOOST_NO_CXX17_HDR_OPTIONAL -template< class T > +template< class Ctx, class T > struct to_described_member { using Ds = describe::describe_members< @@ -450,6 +480,7 @@ struct to_described_member result& res; object const& obj; std::size_t count; + Ctx const& ctx; template< class I > void @@ -478,7 +509,7 @@ struct to_described_member # pragma GCC diagnostic ignored "-Wunused" # pragma GCC diagnostic ignored "-Wunused-variable" #endif - auto member_res = try_value_to(found->value()); + auto member_res = try_value_to( found->value(), ctx ); #if defined(__GNUC__) && BOOST_GCC_VERSION >= 80000 && BOOST_GCC_VERSION < 11000 # pragma GCC diagnostic pop #endif @@ -493,10 +524,13 @@ struct to_described_member }; // described classes -template +template< class T, class Ctx > result value_to_impl( - described_class_conversion_tag, try_value_to_tag, value const& jv ) + described_class_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ctx ) { result res; @@ -509,7 +543,7 @@ value_to_impl( return res; } - to_described_member member_converter{res, *obj, 0u}; + to_described_member< Ctx, T > member_converter{ res, *obj, 0u, ctx }; using Ds = typename decltype(member_converter)::Ds; constexpr std::size_t N = mp11::mp_size::value; @@ -530,10 +564,13 @@ value_to_impl( } // described enums -template +template< class T, class Ctx > result value_to_impl( - described_enum_conversion_tag, try_value_to_tag, value const& jv ) + described_enum_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ) { T val = {}; (void)jv; @@ -559,20 +596,22 @@ value_to_impl( //---------------------------------------------------------- // User-provided conversion -template +template< class T, class Ctx > typename std::enable_if< mp11::mp_valid::value, T>::type -value_to_impl( user_conversion_tag, value_to_tag tag, value const& jv ) +value_to_impl( + user_conversion_tag, value_to_tag tag, value const& jv, Ctx const&) { return tag_invoke(tag, jv); } -template +template< class T, class Ctx > typename std::enable_if< !mp11::mp_valid::value, T>::type -value_to_impl( user_conversion_tag, value_to_tag, value const& jv ) +value_to_impl( + user_conversion_tag, value_to_tag, value const& jv, Ctx const& ) { auto res = tag_invoke(try_value_to_tag(), jv); if( res.has_error() ) @@ -580,20 +619,22 @@ value_to_impl( user_conversion_tag, value_to_tag, value const& jv ) return std::move(*res); } -template +template< class T, class Ctx > typename std::enable_if< mp11::mp_valid::value, result>::type -value_to_impl( user_conversion_tag, try_value_to_tag, value const& jv ) +value_to_impl( + user_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { return tag_invoke(try_value_to_tag(), jv); } -template +template< class T, class Ctx > typename std::enable_if< !mp11::mp_valid::value, result>::type -value_to_impl( user_conversion_tag, try_value_to_tag, value const& jv ) +value_to_impl( + user_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { try { @@ -616,10 +657,217 @@ value_to_impl( user_conversion_tag, try_value_to_tag, value const& jv ) } } +//---------------------------------------------------------- +// User-provided context conversion + +template< + class T, + class Ctx, + class Sup = supported_context +> +typename std::enable_if< + mp11::mp_valid< + has_context_conversion_to_impl, + typename Sup::type, + T>::value, + T>::type +value_to_impl( + context_conversion_tag, + value_to_tag tag, + value const& jv, + Ctx const& ctx ) +{ + return tag_invoke( tag, jv, Sup::get(ctx) ); +} + +template< + class T, + class Ctx, + class Sup = supported_context +> +typename std::enable_if< + !mp11::mp_valid< + has_context_conversion_to_impl, + typename Sup::type, + T>::value, + T>::type +value_to_impl( + context_conversion_tag, value_to_tag, value const& jv, Ctx const& ctx ) +{ + auto res = tag_invoke( try_value_to_tag(), jv, Sup::get(ctx) ); + if( res.has_error() ) + throw_system_error( res.error() ); + return std::move(*res); +} + +template< + class T, + class Ctx, + class Sup = supported_context +> +typename std::enable_if< + mp11::mp_valid< + has_nonthrowing_context_conversion_to_impl, + typename Sup::type, + T>::value, + result>::type +value_to_impl( + context_conversion_tag, + try_value_to_tag tag, + value const& jv, + Ctx const& ctx ) +{ + return tag_invoke( tag, jv, Sup::get(ctx) ); +} + +template< + class T, + class Ctx, + class Sup = supported_context +> +typename std::enable_if< + !mp11::mp_valid< + has_nonthrowing_context_conversion_to_impl, + typename Sup::type, + T>::value, + result>::type +value_to_impl( + context_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ctx ) +{ + try + { + return { + boost::system::in_place_value, + tag_invoke( value_to_tag(), jv, Sup::get(ctx) )}; + } + catch( std::bad_alloc const&) + { + throw; + } + catch( system_error const& e) + { + return {boost::system::in_place_error, e.code()}; + } + catch( ... ) + { + error_code ec; + BOOST_JSON_FAIL(ec, error::exception); + return {boost::system::in_place_error, ec}; + } +} + +//---------------------------------------------------------- +// User-provided full context conversion + +template< + class T, + class Ctx, + class Sup = supported_context +> +typename std::enable_if< + mp11::mp_valid< + has_full_context_conversion_to_impl, + typename Sup::type, + T>::value, + T>::type +value_to_impl( + full_context_conversion_tag, + value_to_tag tag, + value const& jv, + Ctx const& ctx ) +{ + return tag_invoke( tag, jv, Sup::get(ctx), ctx ); +} + +template< + class T, + class Ctx, + class Sup = supported_context +> +typename std::enable_if< + !mp11::mp_valid< + has_full_context_conversion_to_impl, + typename Sup::type, + T>::value, + T>::type +value_to_impl( + full_context_conversion_tag, + value_to_tag, + value const& jv, + Ctx const& ctx ) +{ + auto res = tag_invoke( try_value_to_tag(), jv, Sup::get(ctx), ctx ); + if( res.has_error() ) + throw_system_error( res.error() ); + return std::move(*res); +} + +template< + class T, + class Ctx, + class Sup = supported_context +> +typename std::enable_if< + mp11::mp_valid< + has_nonthrowing_full_context_conversion_to_impl, + typename Sup::type, + T>::value, + result>::type +value_to_impl( + full_context_conversion_tag, + try_value_to_tag tag, + value const& jv, + Ctx const& ctx ) +{ + return tag_invoke( tag, jv, Sup::get(ctx), ctx ); +} + +template< + class T, + class Ctx, + class Sup = supported_context +> +typename std::enable_if< + !mp11::mp_valid< + has_nonthrowing_full_context_conversion_to_impl, + typename Sup::type, + T>::value, + result>::type +value_to_impl( + full_context_conversion_tag, + try_value_to_tag, + value const& jv, + Ctx const& ctx ) +{ + try + { + return { + boost::system::in_place_value, + tag_invoke( value_to_tag(), jv, Sup::get(ctx), ctx )}; + } + catch( std::bad_alloc const&) + { + throw; + } + catch( system_error const& e) + { + return {boost::system::in_place_error, e.code()}; + } + catch( ... ) + { + error_code ec; + BOOST_JSON_FAIL(ec, error::exception); + return {boost::system::in_place_error, ec}; + } +} + // no suitable conversion implementation -template +template< class T, class Ctx > T -value_to_impl( no_conversion_tag, value_to_tag, value const& ) +value_to_impl( no_conversion_tag, value_to_tag, value const&, Ctx const& ) { static_assert( !std::is_same::value, @@ -627,15 +875,16 @@ value_to_impl( no_conversion_tag, value_to_tag, value const& ) } // generic wrapper over non-throwing implementations -template +template< class Impl, class T, class Ctx > T -value_to_impl( Impl impl, value_to_tag, value const& jv ) +value_to_impl( Impl impl, value_to_tag, value const& jv, Ctx const& ctx ) { - return value_to_impl( impl, try_value_to_tag(), jv ).value(); + return value_to_impl( impl, try_value_to_tag(), jv, ctx ).value(); } -template -using value_to_category = conversion_category; +template< class Ctx, class T > +using value_to_category = conversion_category< + Ctx, T, value_to_conversion >; } // detail diff --git a/include/boost/json/fwd.hpp b/include/boost/json/fwd.hpp index b99f322c..fa6dd715 100644 --- a/include/boost/json/fwd.hpp +++ b/include/boost/json/fwd.hpp @@ -63,13 +63,23 @@ struct is_described_enum; template void value_from( T&& t, value& jv ); +template +void value_from( T&& t, value& jv, Context const& ctx ); + template T value_to( value const & v ); +template +T value_to( value const & v, Context const& ctx ); + template typename result_for::type try_value_to( value const & jv ); +template +typename result_for::type +try_value_to( value const & jv, Context const& ctx ); + template typename result_for::type result_from_errno( int e, boost::source_location const* loc ) noexcept; diff --git a/include/boost/json/impl/conversion.hpp b/include/boost/json/impl/conversion.hpp index a50d1040..342dc7ca 100644 --- a/include/boost/json/impl/conversion.hpp +++ b/include/boost/json/impl/conversion.hpp @@ -22,6 +22,7 @@ #include #include +#include #ifndef BOOST_NO_CXX17_HDR_VARIANT # include #endif // BOOST_NO_CXX17_HDR_VARIANT @@ -135,6 +136,8 @@ using value_from_conversion = mp11::mp_true; using value_to_conversion = mp11::mp_false; struct user_conversion_tag { }; +struct context_conversion_tag : user_conversion_tag { }; +struct full_context_conversion_tag : context_conversion_tag { }; struct native_conversion_tag { }; struct value_conversion_tag : native_conversion_tag { }; struct object_conversion_tag : native_conversion_tag { }; @@ -151,25 +154,61 @@ struct described_class_conversion_tag { }; struct described_enum_conversion_tag { }; struct no_conversion_tag { }; +template +using supports_tag_invoke = decltype(tag_invoke( std::declval()... )); + template -using has_user_conversion_from_impl - = decltype(tag_invoke( - value_from_tag(), std::declval(), std::declval())); +using has_user_conversion_from_impl = supports_tag_invoke< + value_from_tag, value&, T&& >; template -using has_user_conversion_to_impl - = decltype(tag_invoke(value_to_tag(), std::declval())); +using has_user_conversion_to_impl = supports_tag_invoke< + value_to_tag, value const& >; template -using has_nonthrowing_user_conversion_to_impl - = decltype(tag_invoke( - try_value_to_tag(), std::declval() )); -template -using has_user_conversion = mp11::mp_if< +using has_nonthrowing_user_conversion_to_impl = supports_tag_invoke< + try_value_to_tag, value const& >; +template< class T, class Dir > +using has_user_conversion1 = mp11::mp_if< std::is_same, mp11::mp_valid, mp11::mp_or< mp11::mp_valid, mp11::mp_valid>>; +template< class Ctx, class T > +using has_context_conversion_from_impl = supports_tag_invoke< + value_from_tag, value&, T&&, Ctx const& >; +template< class Ctx, class T > +using has_context_conversion_to_impl = supports_tag_invoke< + value_to_tag, value const&, Ctx const& >; +template< class Ctx, class T > +using has_nonthrowing_context_conversion_to_impl = supports_tag_invoke< + try_value_to_tag, value const&, Ctx const& >; +template< class Ctx, class T, class Dir > +using has_user_conversion2 = mp11::mp_if< + std::is_same, + mp11::mp_valid, + mp11::mp_or< + mp11::mp_valid, + mp11::mp_valid>>; + +template< class Ctx, class T > +using has_full_context_conversion_from_impl = supports_tag_invoke< + value_from_tag, value&, T&&, Ctx const&, Ctx const& >; +template< class Ctx, class T > +using has_full_context_conversion_to_impl = supports_tag_invoke< + value_to_tag, value const&, Ctx const&, Ctx const& >; +template< class Ctx, class T > +using has_nonthrowing_full_context_conversion_to_impl = supports_tag_invoke< + try_value_to_tag, value const&, Ctx const&, Ctx const& >; +template< class Ctx, class T, class Dir > +using has_user_conversion3 = mp11::mp_if< + std::is_same, + mp11::mp_valid, + mp11::mp_or< + mp11::mp_valid, + mp11::mp_valid< + has_nonthrowing_full_context_conversion_to_impl, Ctx, T>>>; + template< class T > using described_non_public_members = describe::describe_members< T, describe::mod_private | describe::mod_protected>; @@ -177,43 +216,81 @@ template< class T > using described_bases = describe::describe_bases< T, describe::mod_any_access>; -template -using conversion_category = mp11::mp_cond< - // user conversion (via tag_invoke) - has_user_conversion, user_conversion_tag, - // 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, - // failed to find a suitable implementation - mp11::mp_true, no_conversion_tag>; +template< class Ctx, class T, class Dir > +struct conversion_category_impl +{ + using type = 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) + 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, + // failed to find a suitable implementation + mp11::mp_true, no_conversion_tag>; +}; +template< class Ctx, class T, class Dir > +using conversion_category = + typename conversion_category_impl< Ctx, T, Dir >::type; + +template< class T > +using any_conversion_tag = mp11::mp_not< + std::is_same< T, no_conversion_tag > >; + +template< class T, class Dir, class... Ctxs > +struct conversion_category_impl< std::tuple, T, Dir > +{ + using ctxs = mp11::mp_list< remove_cvref... >; + using cats = mp11::mp_list< + conversion_category, T, Dir>... >; + + template< class I > + using exists = mp11::mp_less< I, mp11::mp_size >; + + using context2 = mp11::mp_find< cats, full_context_conversion_tag >; + using context1 = mp11::mp_find< cats, context_conversion_tag >; + using context0 = mp11::mp_find< cats, user_conversion_tag >; + using index = mp11::mp_cond< + exists, context2, + exists, context1, + exists, context0, + mp11::mp_true, mp11::mp_find_if< cats, any_conversion_tag > >; + using type = mp11::mp_eval_or< + no_conversion_tag, + mp11::mp_at, cats, index >; +}; + +struct no_context +{}; template using can_convert = mp11::mp_not< std::is_same< - detail::conversion_category, + detail::conversion_category, detail::no_conversion_tag>>; template using conversion_round_trips_helper = mp11::mp_or< std::is_same, - std::is_same, - std::is_same>; -template + std::is_base_of, + std::is_base_of>; +template< class Ctx, class T, class Dir > using conversion_round_trips = conversion_round_trips_helper< - conversion_category, - conversion_category>>; + conversion_category, + conversion_category>>; template< class T1, class T2 > struct copy_cref_helper @@ -251,6 +328,37 @@ template< class Rng > using forwarded_value = forwarded_value_helper< Rng, iterator_traits< Rng > >; +template< class Ctx, class T, class Dir > +struct supported_context +{ + using type = Ctx; + + static + type const& + get( Ctx const& ctx ) noexcept + { + return ctx; + } +}; + +template< class T, class Dir, class... Ctxs > +struct supported_context< std::tuple, T, Dir > +{ + using Ctx = std::tuple; + using impl = conversion_category_impl; + using index = typename impl::index; + using next_supported = supported_context< + mp11::mp_at< typename impl::ctxs, index >, T, Dir >; + using type = typename next_supported::type; + + static + type const& + get( Ctx const& ctx ) noexcept + { + return next_supported::get( std::get( ctx ) ); + } +}; + } // namespace detail template diff --git a/include/boost/json/value_from.hpp b/include/boost/json/value_from.hpp index b4a2eed6..21669878 100644 --- a/include/boost/json/value_from.hpp +++ b/include/boost/json/value_from.hpp @@ -17,6 +17,162 @@ namespace boost { namespace json { +/** Convert an object of type `T` to @ref value. + + This function attempts to convert an object + of type `T` to @ref value using + + @li one of @ref value's constructors, + + @li a library-provided generic conversion, or + + @li a user-provided overload of `tag_invoke`. + + Out of the box the function supports types satisfying + SequenceContainer, + arrays, arithmetic types, `bool`, `std::tuple`, `std::pair`, + `std::variant`, `std::optional`, `std::monostate`, and `std::nullopt_t`. + + Conversion of other types is done by calling an overload of `tag_invoke` + found by argument-dependent lookup. Its signature should be similar to: + + @code + template< class FullContext > + void tag_invoke( value_from_tag, value&, T, const Context& , const FullContext& ); + @endcode + + or + + @code + void tag_invoke( value_from_tag, value&, T, const Context& ); + @endcode + + or + + @code + void tag_invoke( value_from_tag, value&, T ); + @endcode + + The overloads are checked for existence in that order and the first that + matches will be selected.
+ + The `ctx` argument can be used either as a tag type to provide conversions + for third-party types, or to pass extra data to the conversion function. + + @par Exception Safety + Strong guarantee. + + @tparam T The type of the object to convert. + + @tparam Context The type of context passed to the conversion function. + + @param t The object to convert. + + @param ctx Context passed to the conversion function. + + @param jv @ref value out parameter. + + @see @ref value_from_tag, @ref value_to, + + tag_invoke: A general pattern for supporting customisable functions +*/ +template< class T, class Context > +void +value_from( + T&& t, + Context const& ctx, + value& jv) +{ + using bare_T = detail::remove_cvref; + BOOST_STATIC_ASSERT(detail::conversion_round_trips< + Context, bare_T, detail::value_from_conversion>::value); + using cat = detail::value_from_category; + detail::value_from_impl( cat(), jv, std::forward(t), ctx ); +} + +/** Convert an object of type `T` to @ref value. + + This function attempts to convert an object + of type `T` to @ref value using + + @li one of @ref value's constructors, + + @li a library-provided generic conversion, or + + @li a user-provided overload of `tag_invoke`. + + Out of the box the function supports types satisfying + SequenceContainer, + arrays, arithmetic types, `bool`, `std::tuple`, `std::pair`, + `std::variant`, `std::optional`, `std::monostate`, and `std::nullopt_t`. + + Conversion of other types is done by calling an overload of `tag_invoke` + found by argument-dependent lookup. Its signature should be similar to: + + @code + template< class FullContext > + void tag_invoke( value_from_tag, value&, T, const Context& , const FullContext& ); + @endcode + + or + + @code + void tag_invoke( value_from_tag, value&, T, const Context& ); + @endcode + + or + + @code + void tag_invoke( value_from_tag, value&, T ); + @endcode + + The overloads are checked for existence in that order and the first that + matches will be selected.
+ + A @ref value constructed with the @ref storage_ptr passed to + @ref value_from is passed as the second argument to ensure that the memory + resource is correctly propagated. + + @par Exception Safety + Strong guarantee. + + @tparam T The type of the object to convert. + + @tparam Context The type of context passed to the conversion function. + + @returns `t` converted to @ref value. + + @param t The object to convert. + + @param ctx Context passed to the conversion function. + + @param sp A storage pointer referring to the memory resource + to use for the returned @ref value. The default argument for this + parameter is `{}`. + + @see @ref value_from_tag, @ref value_to, + + tag_invoke: A general pattern for supporting customisable functions +*/ +template< class T, class Context > +#ifndef BOOST_JSON_DOCS +typename std::enable_if< + !std::is_same< detail::remove_cvref, storage_ptr >::value && + !std::is_same< detail::remove_cvref, value >::value, + value >::type +#else +value +#endif +value_from( + T&& t, + Context const& ctx, + storage_ptr sp = {}) +{ + value jv(std::move(sp)); + value_from( static_cast(t), ctx, jv ); + return jv; +} + /** Convert an object of type `T` to @ref value. This function attempts to convert an object @@ -59,11 +215,7 @@ value_from( T&& t, value& jv) { - using bare_T = detail::remove_cvref; - BOOST_STATIC_ASSERT(detail::conversion_round_trips< - bare_T, detail::value_from_conversion>::value); - using cat = detail::value_from_category; - detail::value_from_impl( cat(), jv, std::forward(t) ); + value_from( static_cast(t), detail::no_context(), jv ); } /** Convert an object of type `T` to @ref value. @@ -117,10 +269,8 @@ value_from( T&& t, storage_ptr sp = {}) { - value jv(std::move(sp)); - json::value_from( - std::forward(t), jv); - return jv; + return value_from( + static_cast(t), detail::no_context(), std::move(sp) ); } /** Determine if `T` can be converted to @ref value. diff --git a/include/boost/json/value_to.hpp b/include/boost/json/value_to.hpp index 0d3f3394..8787193d 100644 --- a/include/boost/json/value_to.hpp +++ b/include/boost/json/value_to.hpp @@ -37,7 +37,86 @@ namespace json { found by argument-dependent lookup. Its signature should be similar to: @code - T tag_invoke( value_to_tag, value ); + template< class FullContext > + T tag_invoke( value_to_tag, const value&, const Context& , const FullContext& ); + @endcode + + or + + @code + T tag_invoke( value_to_tag, const value&, const Context& ); + @endcode + + or + + @code + result tag_invoke( value_to_tag, const value& ); + @endcode + + The overloads are checked for existence in that order and the first that + matches will be selected.
+ + The object returned by the function call is returned by @ref value_to as + the result of the conversion.
+ + The `ctx` argument can be used either as a tag type to provide conversions + for third-party types, or to pass extra data to the conversion function. + + @par Constraints + @code + ! std::is_reference< T >::value + @endcode + + @par Exception Safety + Strong guarantee. + + @tparam T The type to convert to. + + @tparam Context The type of context passed to the conversion function. + + @returns `jv` converted to `result`. + + @param jv The @ref value to convert. + + @param ctx Context passed to the conversion function. + + @see @ref value_to_tag, @ref value_from, + + tag_invoke: A general pattern for supporting customisable functions +*/ +template< class T, class Context > +T +value_to( value const& jv, Context const& ctx ) +{ + BOOST_STATIC_ASSERT(! std::is_reference::value); + using bare_T = detail::remove_cvref; + BOOST_STATIC_ASSERT(detail::conversion_round_trips< + Context, bare_T, detail::value_to_conversion>::value); + using cat = detail::value_to_category; + return detail::value_to_impl( cat(), value_to_tag(), jv, ctx ); +} + +/** Convert a @ref value to an object of type `T`. + + This function attempts to convert a @ref value + to `T` using + + @li one of @ref value's accessors, or + + @li a library-provided generic conversion, or + + @li a user-provided overload of `tag_invoke`. + + Out of the box the function supports types satisfying + SequenceContainer, + arrays, arithmetic types, `bool`, `std::tuple`, `std::pair`, + `std::variant`, `std::optional`, `std::monostate`, and `std::nullopt_t`. + + Conversion of other types is done by calling an overload of `tag_invoke` + found by argument-dependent lookup. Its signature should be similar to: + + @code + T tag_invoke( value_to_tag, const value& ); @endcode The object returned by the function call is @@ -66,12 +145,7 @@ template T value_to(const value& jv) { - BOOST_STATIC_ASSERT(! std::is_reference::value); - using bare_T = detail::remove_cvref; - BOOST_STATIC_ASSERT(detail::conversion_round_trips< - bare_T, detail::value_to_conversion>::value); - using cat = detail::value_to_category; - return detail::value_to_impl( cat(), value_to_tag(), jv ); + return value_to( jv, detail::no_context() ); } /** Convert a @ref value to a @ref result of `T`. @@ -94,13 +168,97 @@ value_to(const value& jv) found by argument-dependent lookup. Its signature should be similar to: @code - result tag_invoke( try_value_to_tag, value const& ); + template< class FullContext > + result tag_invoke( try_value_to_tag, const value&, const Context& , const FullContext& ); + @endcode + + or + + @code + result tag_invoke( try_value_to_tag, const value&, const Context& ); + @endcode + + or + + @code + result tag_invoke( try_value_to_tag, const value& ); + @endcode + + The overloads are checked for existence in that order and the first that + matches will be selected.
+ + If an error occurs during conversion, the result will store the error code + associated with the error. If an exception is thrown, the function will + attempt to retrieve the associated error code and return it, otherwise it + will return `error::exception`, unless the exception type is + `std::bad_alloc`, which will be allowed to propagate.
+ + The `ctx` argument can be used either as a tag type to provide conversions + for third-party types, or to pass extra data to the conversion function. + + @par Constraints + @code + ! std::is_reference< T >::value + @endcode + + @par Exception Safety + Strong guarantee. + + @tparam T The type to convert to. + + @tparam Context The type of context passed to the conversion function. + + @param jv The @ref value to convert. + + @param ctx Context passed to the conversion function. + + @returns `jv` converted to `result`. + + @see @ref value_to_tag, @ref value_to, @ref value_from, + + tag_invoke: A general pattern for supporting customisable functions +*/ +template< class T, class Context > +typename result_for::type +try_value_to( value const& jv, Context const& ctx ) +{ + BOOST_STATIC_ASSERT(! std::is_reference::value); + using bare_T = detail::remove_cvref; + BOOST_STATIC_ASSERT(detail::conversion_round_trips< + Context, bare_T, detail::value_to_conversion>::value); + using cat = detail::value_to_category; + return detail::value_to_impl( + cat(), try_value_to_tag(), jv, ctx ); +} + +/** Convert a @ref value to a @ref result of `T`. + + This function attempts to convert a @ref value + to `result` using + + @li one of @ref value's accessors, or + + @li a library-provided generic conversion, or + + @li a user-provided overload of `tag_invoke`. + + Out of the box the function supports types satisfying + SequenceContainer, + arrays, arithmetic types, `bool`, `std::tuple`, `std::pair`, + `std::variant`, `std::optional`, `std::monostate`, and `std::nullopt_t`. + + Conversion of other types is done by calling an overload of `tag_invoke` + found by argument-dependent lookup. Its signature should be similar to: + + @code + result tag_invoke( try_value_to_tag, const value& ); @endcode If an error occurs during conversion, the result will store the error code associated with the error. If an exception is thrown, the function will attempt to retrieve the associated error code and return it, otherwise it - will return `error::exception`. + will return `error::exception`, unless the exception type is + `std::bad_alloc`, which will be allowed to propagate. @par Constraints @code @@ -124,13 +282,7 @@ template typename result_for::type try_value_to(const value& jv) { - BOOST_STATIC_ASSERT(! std::is_reference::value); - using bare_T = detail::remove_cvref; - BOOST_STATIC_ASSERT(detail::conversion_round_trips< - bare_T, detail::value_to_conversion>::value); - using cat = detail::value_to_category; - return detail::value_to_impl( - cat(), try_value_to_tag(), jv ); + return try_value_to( jv, detail::no_context() ); } /** Convert a @ref value to an object of type `T`. diff --git a/test/Jamfile b/test/Jamfile index 7d533866..b2302f93 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -60,7 +60,10 @@ project : requirements . ; for local f in $(SOURCES) { - run $(f) main.cpp /boost/json//boost_json ; + run $(f) main.cpp /boost/json//boost_json + : requirements + msvc:_CRT_SECURE_NO_WARNINGS + ; } run limits.cpp main.cpp /boost/json//json_sources diff --git a/test/doc_forward_conversion.cpp b/test/doc_forward_conversion.cpp index 0f3a967d..a1fa4807 100644 --- a/test/doc_forward_conversion.cpp +++ b/test/doc_forward_conversion.cpp @@ -32,6 +32,31 @@ try_value_to( const value& jv ); } //] +//[doc_forward_conversion_3 +namespace boost { +namespace json { + +class value; + +struct value_from_tag; + +template< class T > +struct try_value_to_tag; + +template< class T1, class T2 > +struct result_for; + +template< class T, class Context > +void value_from( T&& t, value& jv, const Context& ctx ); + +template< class T, class Context > +typename result_for< T, value >::type +try_value_to( const value& jv, const Context& ctx ); + +} +} +//] + #include "doc_types.hpp" #include @@ -41,7 +66,8 @@ namespace user_ns { template< class JsonValue > -void tag_invoke( const boost::json::value_from_tag&, JsonValue& jv, const ip_address& addr ) +void tag_invoke( + const boost::json::value_from_tag&, JsonValue& jv, const ip_address& addr ) { const unsigned char* b = addr.begin(); jv = { b[0], b[1], b[2], b[3] }; @@ -84,6 +110,50 @@ tag_invoke( } //] +//[doc_forward_conversion_4 +namespace user_ns +{ + +struct as_string +{ }; + +template< class JsonValue > +void +tag_invoke( + const boost::json::value_from_tag&, JsonValue& jv, + const ip_address& addr, + const as_string& ) +{ + auto& js = jv.emplace_string(); + js.resize( 4 * 3 + 3 + 1 ); // XXX.XXX.XXX.XXX\0 + auto it = addr.begin(); + auto n = std::sprintf( + js.data(), "%hhu.%hhu.%hhu.%hhu", it[0], it[1], it[2], it[3] ); + js.resize(n); +} + +template< class JsonValue > +typename boost::json::result_for< ip_address, JsonValue >::type +tag_invoke( + const boost::json::try_value_to_tag< ip_address >&, + const JsonValue& jv, + const as_string& ) +{ + const auto* js = jv.if_string(); + if( ! js ) + return make_error_code( std::errc::invalid_argument ); + + unsigned char octets[4]; + int result = std::sscanf( + js->data(), "%hhu.%hhu.%hhu.%hhu", octets, octets + 1, octets + 2, octets + 3 ); + if( result != 4 ) + return make_error_code( std::errc::invalid_argument ); + + return ip_address( octets[0], octets[1], octets[2], octets[3] ); +} + +} +//] #include #include @@ -100,7 +170,7 @@ public: void run() { - value const jv{ 212, 115, 81, 22 }; + value jv{ 212, 115, 81, 22 }; auto const addr = value_to< user_ns::ip_address >( jv ); BOOST_TEST( get<0>(addr) == 212 ); BOOST_TEST( get<1>(addr) == 115 ); @@ -109,6 +179,19 @@ public: value const jv2 = value_from( addr ); BOOST_TEST( jv == jv2 ); + + jv = value_from( addr, user_ns::as_string() ); + BOOST_TEST( jv.is_string() ); + + string const& js = jv.get_string(); + BOOST_TEST( js == "212.115.81.22" ); + + auto const addr2 = value_to< user_ns::ip_address >( + jv, user_ns::as_string() ); + BOOST_TEST( get<0>(addr2) == 212 ); + BOOST_TEST( get<1>(addr2) == 115 ); + BOOST_TEST( get<2>(addr2) == 81 ); + BOOST_TEST( get<3>(addr2) == 22 ); } }; diff --git a/test/doc_types.hpp b/test/doc_types.hpp index 9e9f7bed..1dbb908a 100644 --- a/test/doc_types.hpp +++ b/test/doc_types.hpp @@ -98,6 +98,17 @@ get(ip_address const& addr ) return *(addr.begin() + N); } +inline +bool +operator==(ip_address const& l, ip_address const& r) noexcept +{ + return + get<0>(l) == get<0>(r) && + get<1>(l) == get<1>(r) && + get<2>(l) == get<2>(r) && + get<3>(l) == get<3>(r); +} + } // namespace user_ns #endif // BOOST_JSON_DOC_DOC_TYPES_HPP diff --git a/test/snippets.cpp b/test/snippets.cpp index 2c5848e4..508770d8 100644 --- a/test/snippets.cpp +++ b/test/snippets.cpp @@ -14,7 +14,6 @@ #elif defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wunused" -# pragma clang diagnostic ignored "-Wmismatched-tags" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused" @@ -111,6 +110,147 @@ tag_invoke( const try_value_to_tag< ip_address >&, value const& jv ) } // namespace user_ns +//[doc_context_conversion_1 +namespace user_ns +{ + +struct as_string +{ }; + +void +tag_invoke( + boost::json::value_from_tag, boost::json::value& jv, const ip_address& addr, const as_string& ) +{ + boost::json::string& js = jv.emplace_string(); + js.resize( 4 * 3 + 3 + 1 ); // XXX.XXX.XXX.XXX\0 + auto it = addr.begin(); + auto n = std::sprintf( + js.data(), "%hhu.%hhu.%hhu.%hhu", it[0], it[1], it[2], it[3] ); + js.resize(n); +} + +ip_address +tag_invoke( + boost::json::value_to_tag, const boost::json::value& jv, const as_string& ) +{ + const boost::json::string& js = jv.as_string(); + + unsigned char octets[4]; + int result = std::sscanf( + js.data(), "%hhu.%hhu.%hhu.%hhu", octets, octets + 1, octets + 2, octets + 3 ); + if( result != 4 ) + throw std::invalid_argument("not an IP address"); + + return ip_address( octets[0], octets[1], octets[2], octets[3] ); +} + +} +//] + +//[doc_context_conversion_4 +namespace user_ns +{ + +struct as_iso_8601 +{ }; + +void +tag_invoke( + boost::json::value_from_tag, boost::json::value& jv, std::chrono::system_clock::time_point tp, const as_iso_8601& ) +{ + boost::json::string& js = jv.emplace_string(); + js.resize( 4 + 2 * 5 + 5 + 1 ); // YYYY-mm-ddTHH:MM:ss\0 + + std::time_t t = std::chrono::system_clock::to_time_t( tp ); + std::tm tm = *std::gmtime( &t ); + std::size_t n = std::strftime( + js.data(), js.size(), "%FT%T", &tm ); + js.resize(n); +} + +} +//] + +//[doc_context_conversion_6 +namespace user_ns +{ + +struct date_format +{ + std::string format; + std::size_t buffer_size; +}; + +void +tag_invoke( + boost::json::value_from_tag, boost::json::value& jv, std::chrono::system_clock::time_point tp, const date_format& ctx ) +{ + boost::json::string& js = jv.emplace_string(); + js.resize( ctx.buffer_size ); + + std::time_t t = std::chrono::system_clock::to_time_t( tp ); + std::size_t n = std::strftime( + js.data(), js.size(), ctx.format.c_str(), std::gmtime( &t ) ); + js.resize(n); +} + +} +//] + +//[doc_context_conversion_10 +namespace user_ns +{ + +struct maps_as_objects +{ }; + +template< + class Key, + class Value, + class Ctx > +void +tag_invoke( + boost::json::value_from_tag, + boost::json::value& jv, + const std::map& m, + const maps_as_objects&, + const Ctx& ctx ) +{ + boost::json::object& jo = jv.emplace_object(); + + for( const auto& item: m ) + { + auto k = boost::json::value_from( item.first, ctx, jo.storage() ); + auto v = boost::json::value_from( item.second, ctx, jo.storage() ); + jo[std::move( k.as_string() )] = std::move( v ); + } +} + +template< + class Key, + class Value, + class Ctx > +std::map +tag_invoke( + boost::json::value_to_tag< std::map >, + boost::json::value const& jv, + const maps_as_objects&, + const Ctx& ctx ) +{ + const boost::json::object& jo = jv.as_object(); + std::map< Key, Value > result; + for( auto&& item: jo ) + { + Key k = boost::json::value_to< Key >( item.key(), ctx ); + Value v = boost::json::value_to< Value >( item.value(), ctx ); + result.emplace( std::move(k), std::move(v) ); + } + return result; +} + +} +//] + namespace boost { namespace json { @@ -874,6 +1014,114 @@ usingSpecializedTrait() assert( jv1 == jv2 ); } +void +usingContextualConversions() +{ + using namespace user_ns; + using namespace boost::json; + { +//[doc_context_conversion_2 + ip_address addr( 192, 168, 10, 11 ); + + value jv = value_from( addr, as_string() ); + assert( jv == parse(R"( "192.168.10.11" )") ); + + ip_address addr2 = value_to< ip_address >( jv, as_string() ); + assert(std::equal( + addr.begin(), addr.end(), addr2.begin() )); +//] + (void)addr2; + } + + { +//[doc_context_conversion_3 + std::map< std::string, ip_address > computers = { + { "Alex", { 192, 168, 1, 1 } }, + { "Blake", { 192, 168, 1, 2 } }, + { "Carol", { 192, 168, 1, 3 } }, + }; + value jv = value_from( computers, as_string() ); + assert( jv == parse( + "{ " + " \"Alex\" : \"192.168.1.1\", " + " \"Blake\": \"192.168.1.2\", " + " \"Carol\": \"192.168.1.3\" " + "} " + ) ); +//] + (void)jv; + } + + { +//[doc_context_conversion_5 + std::chrono::system_clock::time_point tp; + value jv = value_from( tp, as_iso_8601() ); + assert( jv == parse(R"( "1970-01-01T00:00:00" )") ); +//] + (void)jv; + } + + { +//[doc_context_conversion_7 + std::chrono::system_clock::time_point tp; + + value jv = value_from( tp, date_format{ "%T %D", 18 } ); + assert( jv == parse(R"( "00:00:00 01/01/70" )") ); + + jv = value_from( tp, as_iso_8601() ); + assert( jv == parse(R"( "1970-01-01T00:00:00" )") ); +//] + (void)jv; + } + + { +//[doc_context_conversion_8 + using time_point = std::chrono::system_clock::time_point; + time_point start; + std::vector< std::pair > log = { + { start += std::chrono::seconds(10), {192, 168, 10, 11} }, + { start += std::chrono::hours(2), {192, 168, 10, 13} }, + { start += std::chrono::minutes(14), {192, 168, 10, 10} }, + }; + value jv = value_from( + log, std::make_tuple( as_string(), as_iso_8601() ) ); + assert( jv == parse( + " [ " + " [ \"1970-01-01T00:00:10\", \"192.168.10.11\" ], " + " [ \"1970-01-01T02:00:10\", \"192.168.10.13\" ], " + " [ \"1970-01-01T02:14:10\", \"192.168.10.10\" ] " + " ] " + ) ); +//] + (void)jv; + } + + { + using time_point = std::chrono::system_clock::time_point; + time_point start; +//[doc_context_conversion_9 + + std::map< time_point, ip_address > log = { + { start += std::chrono::seconds(10), {192, 168, 10, 11} }, + { start += std::chrono::hours(2), {192, 168, 10, 13} }, + { start += std::chrono::minutes(14), {192, 168, 10, 10} }, + }; + + value jv = value_from( + log, + std::make_tuple( maps_as_objects(), as_string(), as_iso_8601() ) ); + assert( jv == parse( + " { " + " \"1970-01-01T00:00:10\": \"192.168.10.11\", " + " \"1970-01-01T02:00:10\": \"192.168.10.13\", " + " \"1970-01-01T02:14:10\": \"192.168.10.10\" " + " } " + ) ); +//] + (void)jv; + } +} + } // namespace class snippets_test @@ -891,6 +1139,7 @@ public: usingPointer(); usingSpecializedTrait(); usingSetAtPointer(); + usingContextualConversions(); BOOST_TEST_PASS(); } diff --git a/test/value_from.cpp b/test/value_from.cpp index 2e90c345..b7703a28 100644 --- a/test/value_from.cpp +++ b/test/value_from.cpp @@ -13,6 +13,7 @@ #include // prevent intellisense bugs #include +#include #include #include @@ -185,6 +186,64 @@ BOOST_DESCRIBE_STRUCT(T11, (T10), (s)) BOOST_DEFINE_ENUM_CLASS(E1, a, b, c) +//---------------------------------------------------------- + +struct custom_context +{ }; + +struct T12 +{ }; + +void +tag_invoke( + ::boost::json::value_from_tag, + ::boost::json::value& jv, + T12 const&, + custom_context const& ) +{ + jv = "T12"; +} + +//---------------------------------------------------------- + +struct another_context +{ }; + +struct T13 +{ }; + +void +tag_invoke( + ::boost::json::value_from_tag, + ::boost::json::value& jv, + T13 const&, + another_context const& ) +{ + jv = "T13"; +} + +//---------------------------------------------------------- + +template< class T, class Ctx > +void +tag_invoke( + ::boost::json::value_from_tag, + ::boost::json::value& jv, + std::vector< T > const& vec, + another_context const&, + Ctx const& ctx ) +{ + boost::json::object& jo = jv.emplace_object(); + unsigned i = 0; + for( auto&& item: vec ) + { + jo.emplace( + std::to_string(++i), + boost::json::value_from( item, ctx, jo.storage() ) ); + } +} + + } // namespace value_from_test_ns template @@ -225,20 +284,13 @@ struct is_described_class<::value_from_test_ns::T11> namespace { -template +template< class T, class... Context > static void -testValueCtor(T const& t) +testValueCtor( T const& t, Context const& ... ctx ) { - BOOST_TEST( serialize(value_from(t)) == serialize(value(t)) ); -} - -template -static -void -testValueCtor() -{ - testValueCtor(T{}); + BOOST_TEST( + serialize(value_from( t, ctx... )) == serialize(value(t)) ); } } // namespace @@ -269,33 +321,35 @@ BOOST_STATIC_ASSERT(has_value_from>::value); class value_from_test { public: + template< class... Context > static void - testValueCtors() + testValueCtors( Context const& ... ctx ) { // value_from supports every value constructor - testValueCtor(); + testValueCtor( value(), ctx... ); char const* s = "5"; - testValueCtor(s); + testValueCtor( s, ctx... ); } + template< class... Context > static void - testGeneral() + testGeneral( Context const& ... ctx ) { { int a[4] = {1, 2, 3, 4}; value b{1, 2, 3, 4}; - value c = value_from(a); + value c = value_from( a, ctx... ); BOOST_TEST(c.is_array()); BOOST_TEST(serialize(c) == serialize(b)); } { std::tuple a{1, "2", 42, true}; value b{1, "2", 42, true}; - value c = value_from(a); + value c = value_from( a, ctx... ); BOOST_TEST(c.is_array()); BOOST_TEST(serialize(c) == serialize(b)); } @@ -307,12 +361,12 @@ public: array& b_arr = b.emplace_array(); b_arr.insert(b_arr.end(), a.begin(), a.end()); - BOOST_TEST(value_from(a) == b); + BOOST_TEST(value_from( a, ctx... ) == b); } { std::pair a{1, string("2")}; value b{1, "2"}; - value c = value_from(a); + value c = value_from( a, ctx... ); BOOST_TEST(c.is_array()); BOOST_TEST(serialize(c) == serialize(b)); } @@ -320,33 +374,34 @@ public: // ensures that this isn't parsed as a key value pair std::pair a{"2", 1}; value b{"2", 1}; - value c = value_from(a); + value c = value_from( a, ctx... ); BOOST_TEST(c.is_array()); BOOST_TEST(serialize(c) == serialize(b)); } { key_value_pair a{"2", 1}; value b{"2", 1}; - value c = value_from(a); + value c = value_from( a, ctx... ); BOOST_TEST(c.is_array()); BOOST_TEST(serialize(c) == serialize(b)); } { ::value_from_test_ns::T7 a; - value b = value_from(a); + value b = value_from( a, ctx... ); BOOST_TEST(b.is_null()); } } + template< class... Context > static void - testAssociative() + testAssociative( Context const& ... ctx ) { { std::map a = {{"a", 1}, {"b", 2}, {"c", 3}}; value b = {{"a", 1}, {"b", 2}, {"c", 3}}; - value c = value_from(a); + value c = value_from( a, ctx... ); BOOST_TEST(c.is_object()); BOOST_TEST(a.size() == c.as_object().size()); BOOST_TEST(b.as_object().size() == c.as_object().size()); @@ -355,7 +410,7 @@ public: std::unordered_map a = {{"a", 1}, {"b", 2}, {"c", 3}}; value b = {{"a", 1}, {"b", 2}, {"c", 3}}; - value c = value_from(a); + value c = value_from( a, ctx... ); BOOST_TEST(c.is_object()); BOOST_TEST(a.size() == c.as_object().size()); BOOST_TEST(b.as_object().size() == c.as_object().size()); @@ -364,7 +419,7 @@ public: std::map a = {{1, 1}, {2, 2}, {3, 3}}; value b = {{1, 1}, {2, 2}, {3, 3}}; - value c = value_from(a); + value c = value_from( a, ctx... ); BOOST_TEST(!c.is_object()); BOOST_TEST(a.size() == c.as_array().size()); BOOST_TEST(b.as_array().size() == c.as_array().size()); @@ -373,19 +428,20 @@ public: std::unordered_map a = {{1, 1}, {2, 2}, {3, 3}}; value b = {{1, 1}, {2, 2}, {3, 3}}; - value c = value_from(a); + value c = value_from( a, ctx... ); BOOST_TEST(!c.is_object()); BOOST_TEST(a.size() == c.as_array().size()); BOOST_TEST(b.as_array().size() == c.as_array().size()); } } + template< class... Context > static void - testPreferUserCustomizations() + testPreferUserCustomizations( Context const& ... ctx ) { value_from_test_ns::T5 t5; - BOOST_TEST((::boost::json::value_from(t5) == "T5")); + BOOST_TEST((::boost::json::value_from( t5, ctx... ) == "T5")); } void @@ -413,62 +469,137 @@ public: } } + template< class... Context > + static void - testDescribed() + testDescribed( Context const& ... ctx ) { + ignore_unused( ctx... ); #ifdef BOOST_DESCRIBE_CXX14 ::value_from_test_ns::T10 t10{909, -1.45}; - value jv = value_from(t10); + value jv = value_from( t10, ctx... ); BOOST_TEST(( jv == value{{"n", 909}, {"d", -1.45}} )); ::value_from_test_ns::T11 t11; t11.n = 67; t11.d = -.12; t11.s = "qwerty"; - jv = value_from(t11); + jv = value_from( t11, ctx... ); BOOST_TEST(( jv == value{{"n", 67}, {"d", -.12}, {"s", "qwerty"}} )); ::value_from_test_ns::E1 e1 = ::value_from_test_ns::E1::a; - BOOST_TEST( value_from(e1) == "a" ); + BOOST_TEST( value_from( e1, ctx... ) == "a" ); e1 = ::value_from_test_ns::E1::b; - BOOST_TEST( value_from(e1) == "b" ); + BOOST_TEST( value_from( e1, ctx... ) == "b" ); e1 = static_cast<::value_from_test_ns::E1>(1001); - BOOST_TEST( value_from(e1) == 1001 ); + BOOST_TEST( value_from( e1, ctx... ) == 1001 ); #endif } -#ifndef BOOST_NO_CXX17_HDR_OPTIONAL - void testOptional() + template< class... Context > + static + void testOptional( Context const& ... ctx ) { + ignore_unused( ctx... ); +#ifndef BOOST_NO_CXX17_HDR_OPTIONAL std::vector> opts{1, 2, 3, {}, 5}; - value jv = value_from(opts); + value jv = value_from( opts, ctx... ); BOOST_TEST( jv == (value{1, 2, 3, nullptr, 5}) ); - BOOST_TEST( value_from(std::nullopt).is_null() ); - } + BOOST_TEST( value_from( std::nullopt, ctx... ).is_null() ); #endif + } -#ifndef BOOST_NO_CXX17_HDR_VARIANT + template< class... Context > + static void - testVariant() + testVariant( Context const& ... ctx ) { + ignore_unused( ctx... ); +#ifndef BOOST_NO_CXX17_HDR_VARIANT std::variant v = 4; - value jv = value_from(v); + value jv = value_from( v, ctx... ); BOOST_TEST(jv == 4); v = 0.5; - jv = value_from(v); + jv = value_from( v, ctx... ); BOOST_TEST(jv == 0.5); v = ::value_from_test_ns::T5{}; - jv = value_from(v); + jv = value_from( v, ctx... ); BOOST_TEST(jv == "T5"); - BOOST_TEST( value() == value_from(std::monostate()) ); - } + BOOST_TEST( value() == value_from( std::monostate(), ctx... ) ); #endif // BOOST_NO_CXX17_HDR_VARIANT + } + + void + testContext() + { + value jv = value_from( + value_from_test_ns::T1{}, value_from_test_ns::custom_context() ); + BOOST_TEST( jv == 42 ); + + jv = value_from( + value_from_test_ns::T12{}, + value_from_test_ns::custom_context() ); + BOOST_TEST( jv == "T12" ); + + jv = value_from( + value_from_test_ns::T13{}, + std::make_tuple( + value_from_test_ns::custom_context(), + value_from_test_ns::another_context() ) ); + BOOST_TEST( jv == "T13" ); + + jv = value_from( + std::pair(), + std::make_tuple( + value_from_test_ns::custom_context(), + value_from_test_ns::another_context() ) ); + BOOST_TEST( jv == (array{ "T12", "T13" }) ); + + + auto ctx = std::make_tuple( + value_from_test_ns::custom_context(), + value_from_test_ns::another_context() ); + using Ctx = decltype(ctx); + using Sup = detail::supported_context< + Ctx, + std::vector, + detail::value_from_conversion >; + BOOST_STATIC_ASSERT( Sup::index::value == 1 ); + BOOST_STATIC_ASSERT( + std::is_same< + Sup::type, + value_from_test_ns::another_context>::value ); + + jv = value_from( + std::vector(2), + std::make_tuple( + value_from_test_ns::custom_context(), + value_from_test_ns::another_context() ) ); + BOOST_TEST( jv == (object{ {"1", "T12"}, {"2", "T12"} }) ); + } + + struct run_templated_tests + { + // this overload supports zero or one default constructible contexts + // and used with mp_for_each + template< class... Context > + void operator()( mp11::mp_list< Context... > ) + { + testValueCtors( Context()... ); + testGeneral( Context()... ); + testAssociative( Context()... ); + testPreferUserCustomizations( Context()... ); + testDescribed( Context()... ); + testOptional( Context()... ); + testVariant( Context()... ); + } + }; void run() @@ -476,18 +607,24 @@ public: check("42", ::value_from_test_ns::T1{}); check("[[1,2,3,4],\"test\"]", ::value_from_test_ns::T2{}); - testValueCtors(); - testGeneral(); - testAssociative(); - testPreferUserCustomizations(); + mp11::mp_for_each< + mp11::mp_list< + mp11::mp_list<>, + mp11::mp_list, + mp11::mp_list, + mp11::mp_list< + std::tuple>, + mp11::mp_list< + std::tuple< + std::tuple>>, + mp11::mp_list< + std::tuple< + detail::no_context, + value_from_test_ns::custom_context>> + >>( run_templated_tests() ); + + testContext(); testTrySize(); - testDescribed(); -#ifndef BOOST_NO_CXX17_HDR_OPTIONAL - testOptional(); -#endif -#ifndef BOOST_NO_CXX17_HDR_VARIANT - testVariant(); -#endif // BOOST_NO_CXX17_HDR_VARIANT } }; diff --git a/test/value_to.cpp b/test/value_to.cpp index fe282aa4..86fa4eba 100644 --- a/test/value_to.cpp +++ b/test/value_to.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -124,6 +125,26 @@ struct T8 }; BOOST_DESCRIBE_STRUCT(T8, (), (n, d, opt_s)) +//---------------------------------------------------------- + +struct custom_context +{ }; + +struct T9 +{ }; + +boost::json::result +tag_invoke( + boost::json::try_value_to_tag, + boost::json::value const& jv, + custom_context const&) +{ + boost::json::string const* str = jv.if_string(); + if( str && *str == "T9" ) + return T9{}; + return make_error_code(boost::json::error::syntax); +} + } // namespace value_to_test_ns namespace std @@ -173,119 +194,137 @@ class value_to_test { public: - template +#define BOOST_TEST_CONV(x, ... ) \ + BOOST_TEST( value_to( value_from((x)), __VA_ARGS__ ) == (x) ) + + template< class... Context > + static void - check(T t) + testNumberCast( Context const& ... ctx ) { - BOOST_TEST(value_to(value_from(t)) == t); + BOOST_TEST_CONV( (short)-1, ctx... ); + BOOST_TEST_CONV( (int)-2, ctx... ); + BOOST_TEST_CONV( (long)-3, ctx... ); + BOOST_TEST_CONV( (long long)-4, ctx... ); + BOOST_TEST_CONV( (unsigned short)1, ctx... ); + BOOST_TEST_CONV( (unsigned int)2, ctx... ); + BOOST_TEST_CONV( (unsigned long)3, ctx... ); + BOOST_TEST_CONV( (unsigned long long)4, ctx... ); + BOOST_TEST_CONV( (float)1.5, ctx... ); + BOOST_TEST_CONV( (double)2.5, ctx... ); + BOOST_TEST_CONV( true, ctx... ); + BOOST_TEST_THROWS_WITH_LOCATION(value_to( + value(), ctx... )); + BOOST_TEST_THROWS_WITH_LOCATION(value_to( + value(), ctx... )); + BOOST_TEST_THROWS_WITH_LOCATION(value_to( + value(), ctx... )); } + template< class... Context > + static void - testNumberCast() + testJsonTypes( Context const& ... ctx ) { - check((short)-1); - check((int)-2); - check((long)-3); - check((long long)-4); - check((unsigned short)1); - check((unsigned int)2); - check((unsigned long)3); - check((unsigned long long)4); - check((float)1.5); - check((double)2.5); - check(true); - BOOST_TEST_THROWS_WITH_LOCATION( - value_to(value()) ); - BOOST_TEST_THROWS_WITH_LOCATION( - value_to(value()) ); - BOOST_TEST_THROWS_WITH_LOCATION( - value_to(value()) ); + value_to( value(object_kind), ctx... ); + value_to( value(array_kind), ctx... ); + value_to( value(string_kind), ctx... ); + + BOOST_TEST_THROWS_WITH_LOCATION(value_to( + value(), ctx... )); + BOOST_TEST_THROWS_WITH_LOCATION(value_to( + value(), ctx... )); + BOOST_TEST_THROWS_WITH_LOCATION(value_to( + value(), ctx... )); } + template< class... Context > + static void - testJsonTypes() + testGenerics( Context const& ... ctx ) { - value_to(value(object_kind)); - value_to(value(array_kind)); - value_to(value(string_kind)); - - BOOST_TEST_THROWS_WITH_LOCATION( - value_to(value()) ); - BOOST_TEST_THROWS_WITH_LOCATION( - value_to(value()) ); - BOOST_TEST_THROWS_WITH_LOCATION( - value_to(value()) ); - } - - void - testGenerics() - { - check(std::string("test")); - check(std::map - { - {"a", 1}, {"b", 2}, {"c", 3} - }); - check(std::multimap - { - {"2", 4}, {"3", 9}, {"5", 25} - }); - check(std::unordered_map - { - { "a", 1 }, {"b", 2}, {"c", 3} - }); - check(std::vector{1, 2, 3, 4}); - check(std::vector{true, false, false, true}); - check(std::make_pair(std::string("test"), 5)); - check(std::make_tuple(std::string("outer"), - std::make_pair(std::string("test"), 5))); - check(std::map - { - {2, 4}, {3, 9}, {5, 25} - }); + BOOST_TEST_CONV( std::string("test"), ctx... ); + BOOST_TEST_CONV( + (std::map + { + {"a", 1}, {"b", 2}, {"c", 3} + }), + ctx... ); + BOOST_TEST_CONV( + (std::multimap + { + {"2", 4}, {"3", 9}, {"5", 25} + }), + ctx... ); + BOOST_TEST_CONV( + (std::unordered_map + { + { "a", 1 }, {"b", 2}, {"c", 3} + }), + ctx... ); + BOOST_TEST_CONV( (std::vector{1, 2, 3, 4}), ctx... ); + BOOST_TEST_CONV( + (std::vector{true, false, false, true}), ctx... ); + BOOST_TEST_CONV( + std::make_pair(std::string("test"), 5), ctx... ); + BOOST_TEST_CONV( + std::make_tuple( + std::string("outer"), std::make_pair(std::string("test"), 5) ), + ctx... ); + BOOST_TEST_CONV( + (std::map + { + {2, 4}, {3, 9}, {5, 25} + }), + ctx... ); { std::array arr; arr.fill(0); - check(arr); + BOOST_TEST_CONV( arr, ctx... ); } // mismatched type BOOST_TEST_THROWS_WITH_LOCATION( - value_to(value()) ); + value_to( value(), ctx... )); // mismatched type BOOST_TEST_THROWS_WITH_LOCATION( - ( value_to>(value()) )); + (value_to>( value(), ctx... ))); // element fails BOOST_TEST_THROWS_WITH_LOCATION( - ( value_to>( - value{{"1", 1}, {"2", true}, {"3", false}}) )); + (value_to>( + value{{"1", 1}, {"2", true}, {"3", false}}, ctx... ))); // reserve fails BOOST_TEST_THROWS_WITH_LOCATION( - ( value_to( - value{{"1", 1}, {"2", true}, {"3", false}}) )); + (value_to( + value{{"1", 1}, {"2", true}, {"3", false}}, ctx... ))); // mismatched type BOOST_TEST_THROWS_WITH_LOCATION( - value_to>(value()) ); + value_to>( value(), ctx... )); // element fails BOOST_TEST_THROWS_WITH_LOCATION( - value_to>(value{1, 2, false, 3}) ); + value_to>( value{1, 2, false, 3}, ctx... )); // reserve fails BOOST_TEST_THROWS_WITH_LOCATION( - ( value_to>(value{1, 2, 3}) )); + (value_to>( value{1, 2, 3}, ctx... ))); BOOST_TEST_THROWS_WITH_LOCATION( - ( value_to>(value{1, 2, 3, 4, 5}) )); + (value_to>( + value{1, 2, 3, 4, 5}, ctx... ))); // mismatched type BOOST_TEST_THROWS_WITH_LOCATION( - ( value_to>(value()) )); + (value_to>( + value(), ctx... ))); // element fails BOOST_TEST_THROWS_WITH_LOCATION( - ( value_to>(value{1, 2, false}) )); + (value_to>( + value{1, 2, false}, ctx... ))); // reserve fails BOOST_TEST_THROWS_WITH_LOCATION( - ( value_to>(value{1, 2, 3}) )); + (value_to>( + value{1, 2, 3}, ctx... ))); } @@ -314,22 +353,28 @@ public: } } - void testNullptr() + template< class... Context > + static + void testNullptr( Context const& ... ctx ) { - (void)value_to(value()); - (void)value_to<::value_to_test_ns::T1>(value()); + (void)value_to( value(), ctx... ); + (void)value_to<::value_to_test_ns::T1>( value(), ctx... ); BOOST_TEST_THROWS_WITH_LOCATION( - ( value_to(value(1)) )); + value_to( value(1), ctx... )); BOOST_TEST_THROWS_WITH_LOCATION( - ( value_to<::value_to_test_ns::T1>(value(1)) )); + value_to<::value_to_test_ns::T1>( value(1), ctx... )); } - void testDescribed() + template< class... Context > + static + void testDescribed( Context const& ... ctx ) { + ignore_unused( ctx... ); #ifdef BOOST_DESCRIBE_CXX14 { value jv = {{"n", -78}, {"d", 0.125}}; - auto res = try_value_to<::value_to_test_ns::T6>(jv); + auto res = try_value_to<::value_to_test_ns::T6>( + jv, ctx... ); BOOST_TEST( res ); BOOST_TEST( res->n == -78 ); BOOST_TEST( res->d == 0.125 ); @@ -340,7 +385,8 @@ public: } { value jv = {{"n", 1}, {"d", 2}, {"s", "xyz"}}; - auto res = try_value_to<::value_to_test_ns::T7>(jv); + auto res = try_value_to<::value_to_test_ns::T7>( + jv, ctx... ); BOOST_TEST( res ); BOOST_TEST( res->n == 1 ); BOOST_TEST( res->d == 2 ); @@ -348,31 +394,33 @@ public: } BOOST_TEST_THROWS_WITH_LOCATION( - value_to<::value_to_test_ns::T6>( value(1) )); + value_to<::value_to_test_ns::T6>( value(1), ctx... )); BOOST_TEST_THROWS_WITH_LOCATION( - value_to<::value_to_test_ns::T6>( value{{"x", 0}} )); + value_to<::value_to_test_ns::T6>( value{{"x", 0}}, ctx... )); BOOST_TEST_THROWS_WITH_LOCATION( - value_to<::value_to_test_ns::T6>( (value{{"n", 0}, {"x", 0}}) )); + value_to<::value_to_test_ns::T6>( + value{{"n", 0}, {"x", 0}}, ctx... )); { value jv = "a"; - auto e1 = value_to<::value_to_test_ns::E1>(jv); + auto e1 = value_to<::value_to_test_ns::E1>( jv, ctx... ); BOOST_TEST( e1 == ::value_to_test_ns::E1::a ); jv = "b"; - e1 = value_to<::value_to_test_ns::E1>(jv); + e1 = value_to<::value_to_test_ns::E1>( jv, ctx... ); BOOST_TEST( e1 == ::value_to_test_ns::E1::b ); } BOOST_TEST_THROWS_WITH_LOCATION( - value_to<::value_to_test_ns::E1>( value(1) )); + value_to<::value_to_test_ns::E1>( value(1), ctx... )); BOOST_TEST_THROWS_WITH_LOCATION( - value_to<::value_to_test_ns::E1>( value("x") )); + value_to<::value_to_test_ns::E1>( value("x"), ctx... )); { #ifndef BOOST_NO_CXX17_HDR_OPTIONAL value jv = {{"n", -78}, {"d", 0.125}}; - auto res = try_value_to<::value_to_test_ns::T8>(jv); + auto res = try_value_to<::value_to_test_ns::T8>( + jv, ctx... ); BOOST_TEST( res ); BOOST_TEST( res->n == -78 ); BOOST_TEST( res->d == 0.125 ); @@ -380,82 +428,109 @@ public: jv.as_object()["x"] = 0; BOOST_TEST_THROWS_WITH_LOCATION( - value_to<::value_to_test_ns::T8>( jv )); + value_to<::value_to_test_ns::T8>( jv, ctx... )); #endif // BOOST_NO_CXX17_HDR_OPTIONAL } #endif // BOOST_DESCRIBE_CXX14 } -#ifndef BOOST_NO_CXX17_HDR_OPTIONAL - void testOptional() + template< class... Context > + static + void testOptional( Context const& ... ctx ) { + ignore_unused( ctx... ); +#ifndef BOOST_NO_CXX17_HDR_OPTIONAL using Opts = std::vector>; value jv = value{1, nullptr, 3, nullptr, 5}; - auto opts = value_to(jv); + auto opts = value_to( jv, ctx... ); BOOST_TEST( opts == (Opts{1, {}, 3, {}, 5}) ); value_to< std::nullopt_t >(value()); - BOOST_TEST_THROWS_WITH_LOCATION( value_to< std::nullopt_t >(jv) ); - } + BOOST_TEST_THROWS_WITH_LOCATION( + value_to< std::nullopt_t >( jv, ctx... )); #endif + } -#ifndef BOOST_NO_CXX17_HDR_VARIANT + template< class... Context > + static void - testVariant() + testVariant( Context const& ... ctx ) { + ignore_unused( ctx... ); +#ifndef BOOST_NO_CXX17_HDR_VARIANT using Var = std::variant; value jv(4); - auto v = value_to(jv); + auto v = value_to( jv, ctx... ); BOOST_TEST( v.index() == 0 ); BOOST_TEST( std::get<0>(v) == 4 ); jv = "foobar"; - v = value_to(jv); + v = value_to( jv, ctx... ); BOOST_TEST( v.index() == 2 ); BOOST_TEST( std::get<2>(v) == "foobar" ); jv = "T2"; - v = value_to(jv); + v = value_to( jv, ctx... ); BOOST_TEST( v.index() == 1 ); jv = 3.5; - BOOST_TEST_THROWS_WITH_LOCATION( value_to(jv) ); + BOOST_TEST_THROWS_WITH_LOCATION( + value_to( jv, ctx... )); - value_to( value() ); - BOOST_TEST_THROWS_WITH_LOCATION( value_to(jv) ); - } + value_to( value(), ctx... ); + BOOST_TEST_THROWS_WITH_LOCATION( + value_to( jv, ctx... )); #endif // BOOST_NO_CXX17_HDR_VARIANT + } + template< class... Context > + static void - testNonThrowing() + testNonThrowing( Context const& ... ctx ) { // using result { - auto res = try_value_to<::value_to_test_ns::T2>(value()); + // clang 3.8 seems to have some bug when dealing with a lot of + // template instantiations; this assert magically makes the problem + // go away, I assume, by instantiating the needed types beforehand + BOOST_STATIC_ASSERT( + detail::conversion_round_trips< + mp11::mp_first< + mp11::mp_list< + Context..., int> >, + ::value_to_test_ns::T2, + detail::value_to_conversion>::value ); + + auto res = try_value_to<::value_to_test_ns::T2>( + value(), ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error() == error::syntax ); - res = try_value_to<::value_to_test_ns::T2>(value("T2")); + res = try_value_to<::value_to_test_ns::T2>( + value("T2"), ctx... ); BOOST_TEST( res.has_value() ); } // throwing overload falls back to nonthrowing customization { BOOST_TEST_THROWS( - value_to<::value_to_test_ns::T2>(value()), + value_to<::value_to_test_ns::T2>( value(), ctx... ), system_error); } // nonthrowing overload falls back to throwing customization { - auto res = try_value_to<::value_to_test_ns::T3>(value()); + auto res = try_value_to<::value_to_test_ns::T3>( + value(), ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error() == error::not_string ); - res = try_value_to<::value_to_test_ns::T3>(value("")); + res = try_value_to<::value_to_test_ns::T3>( + value(""), ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error() == error::exception ); - res = try_value_to<::value_to_test_ns::T3>(value("T3")); + res = try_value_to<::value_to_test_ns::T3>( + value("T3"), ctx... ); BOOST_TEST( res.has_value() ); } // sequence @@ -463,7 +538,7 @@ public: // wrong input type { auto res = try_value_to< std::vector<::value_to_test_ns::T2> >( - value("not an array")); + value("not an array"), ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error().has_location() ); BOOST_TEST( res.error() == error::not_array ); @@ -472,7 +547,7 @@ public: // wrong input type { auto res = try_value_to< std::array >( - value{1, 2}); + value{1, 2}, ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error().has_location() ); BOOST_TEST( res.error() == error::size_mismatch ); @@ -481,13 +556,13 @@ public: // element error { auto res = try_value_to< std::vector<::value_to_test_ns::T2> >( - value{"T2", "T2", nullptr}); + value{"T2", "T2", nullptr}, ctx... ); BOOST_TEST( res.error() == error::syntax ); } // success auto res = try_value_to< std::vector<::value_to_test_ns::T2> >( - value{"T2", "T2", "T2"}); + value{"T2", "T2", "T2"}, ctx... ); BOOST_TEST( res.has_value() ); BOOST_TEST( res->size() == 3 ); } @@ -496,8 +571,8 @@ public: // wrong input type { auto res = try_value_to< - std::map >( - value("not a map")); + std::map >( + value("not a map"), ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error().has_location() ); BOOST_TEST( res.error() == error::not_object ); @@ -506,7 +581,7 @@ public: // reserve fails { auto res = try_value_to< ::value_to_test_ns::T4 >( - value{{"1", 1}, {"2", 2}, {"3", 3}}); + value{{"1", 1}, {"2", 2}, {"3", 3}}, ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error().has_location() ); BOOST_TEST( res.error() == error::size_mismatch ); @@ -515,16 +590,17 @@ public: // element error { auto res = try_value_to< - std::map >( - value{{"1", "T2"}, {"2", "T2"}, {"3", nullptr}}); + std::map >( + value{{"1", "T2"}, {"2", "T2"}, {"3", nullptr}}, + ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error() == error::syntax ); } // success auto res = try_value_to< - std::map >( - value{{"1", "T2"}, {"2", "T2"}, {"3", "T2"}}); + std::map >( + value{{"1", "T2"}, {"2", "T2"}, {"3", "T2"}}, ctx... ); BOOST_TEST( res.has_value() ); BOOST_TEST( res->size() == 3 ); } @@ -538,7 +614,7 @@ public: bool, std::nullptr_t, ::value_to_test_ns::T2>>( - value("not an array")); + value("not an array"), ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error().has_location() ); BOOST_TEST( res.error() == error::not_array ); @@ -552,7 +628,7 @@ public: bool, std::nullptr_t, ::value_to_test_ns::T2>>( - value{1, 2}); + value{1, 2}, ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error().has_location() ); BOOST_TEST( res.error() == error::size_mismatch ); @@ -566,7 +642,7 @@ public: bool, std::nullptr_t, ::value_to_test_ns::T2>>( - value{1, "foobar", false, nullptr, ""}); + value{1, "foobar", false, nullptr, ""}, ctx... ); BOOST_TEST( res.has_error() ); BOOST_TEST( res.error() == error::syntax ); } @@ -578,7 +654,7 @@ public: bool, std::nullptr_t, ::value_to_test_ns::T2>>( - value{1, "foobar", false, nullptr, "T2"}); + value{1, "foobar", false, nullptr, "T2"}, ctx... ); BOOST_TEST( res.has_value() ); BOOST_TEST( std::get<0>(*res) == 1 ); BOOST_TEST( std::get<1>(*res) == "foobar" ); @@ -586,33 +662,69 @@ public: } // rethrowing bad_alloc BOOST_TEST_THROWS( - try_value_to(value()), + try_value_to( value(), ctx... ), std::bad_alloc); } + template< class... Context > + static void - testUserConversion() + testUserConversion( Context const& ... ctx ) { - value_to(value("T2")); + value_to( value("T2"), ctx... ); } + void + testContext() + { + value_to( + value("T9"), value_to_test_ns::custom_context() ); + + BOOST_TEST_THROWS( + value_to( + value(), value_to_test_ns::custom_context() ), + system_error); + } + + struct run_templated_tests + { + // this overload supports zero or one default constructible contexts + // and used with mp_for_each + template< class... Context > + void operator()( mp11::mp_list< Context... > ) + { + testNumberCast( Context()... ); + testJsonTypes( Context()... ); + testGenerics( Context()... ); + testNullptr( Context()... ); + testDescribed( Context()... ); + testOptional( Context()... ); + testVariant( Context()... ); + testNonThrowing( Context()... ); + testUserConversion( Context()... ); + } + }; + void run() { - testNumberCast(); - testJsonTypes(); - testGenerics(); + mp11::mp_for_each< + mp11::mp_list< + mp11::mp_list<>, + mp11::mp_list, + mp11::mp_list, + mp11::mp_list< + std::tuple>, + mp11::mp_list< + std::tuple< + std::tuple>>, + mp11::mp_list< + std::tuple< + detail::no_context, value_to_test_ns::custom_context>> + >>( run_templated_tests() ); + + testContext(); testContainerHelpers(); - testNullptr(); - testDescribed(); -#ifndef BOOST_NO_CXX17_HDR_OPTIONAL - testOptional(); -#endif -#ifndef BOOST_NO_CXX17_HDR_VARIANT - testVariant(); -#endif // BOOST_NO_CXX17_HDR_VARIANT - testUserConversion(); - testNonThrowing(); } };