diff --git a/doc/externals.hpp b/doc/externals.hpp index 7d803797d..0a35f1e80 100644 --- a/doc/externals.hpp +++ b/doc/externals.hpp @@ -212,6 +212,16 @@ struct bad_alloc {}; /// @see https://en.cppreference.com/w/cpp/container/map struct map {}; +/// !EXTERNAL! +/// +/// @see https://en.cppreference.com/cpp/utility/variant/monostate +struct monostate {}; + +/// !EXTERNAL! +/// +/// @see https://en.cppreference.com/w/cpp/container/multimap +struct multimap {}; + /// !EXTERNAL! /// /// @see https://en.cppreference.com/w/cpp/container/unordered_map diff --git a/doc/pages/conversion/context.adoc b/doc/pages/conversion/context.adoc index a1f4fc6d4..64df50ba7 100644 --- a/doc/pages/conversion/context.adoc +++ b/doc/pages/conversion/context.adoc @@ -12,7 +12,7 @@ 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---context. +achieved with Boost.JSON by providing an extra argument--context. Let's implement conversion from `user_ns::ip_address` to a JSON string: @@ -125,13 +125,12 @@ 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, -<> requires that your key type satisfies -<>. Now, let's say your keys are not string-like, but they -do convert to <>. 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. +Consider this example. <> requires that your key type is of +<>. Now, let's say your keys' category is different but +they do convert to <>. 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. [source] ---- diff --git a/doc/pages/conversion/custom.adoc b/doc/pages/conversion/custom.adoc index e01323b1a..56d25a4ae 100644 --- a/doc/pages/conversion/custom.adoc +++ b/doc/pages/conversion/custom.adoc @@ -9,18 +9,23 @@ Official repository: https://github.com/boostorg/json = Custom Conversions Boost.JSON uses two mechanisms to customize conversion between <> -and user types. One mechanism involves specializing type traits. The other one -is more powerful and requires defining overloads of `tag_invoke`. Both -mechanisms will be further explained in this section. - -== Conversion Traits -Previously a number of conversion type traits, like <> or -<>, were introduced. The library tries the traits one -after another and uses the implementation that corresponds to the first -matching trait. In some cases, though, a type would match a trait with a higher -priority, but the user intends for it to belong to a lower priority category. -If this happens the user can specialize the trait that's not supposed to match -for that type to be an equivalent of `std::false_type`. +and user types. One mechanism involves specializing <>. The +other one is more powerful and requires defining overloads of `tag_invoke`. +Both mechanisms will be further explained in this section. + +== `use_category` + +The library generally deduces the appropriate implementation of conversion for +a given type using internal type traits. These type traits attempt to deduce +the correct conversion category by checking if the type satisfies the +requirements associated with that category. The categories are checked one by +one, in the order of their appearance in the table in the previous subsection, +and the first one whose requirements are satisfied is selected. + +In some cases, though, a type would match a category with a higher priority, +but the user intends for it to belong to a lower priority category. If this +happens the user can specialize the template `use_category` for that type to be +equivalent to the intended category. Consider this type: @@ -33,9 +38,9 @@ It exposes both a sequence API and a tuple API. But converting from <> to `user_ns::ip_address` would not be able to use implementation for sequences, since those are constructed empty and then populated one element at a time, while `ip_address` has a fixed size of 4. The tuple conversion would -fit, though. The only problem is that <> has a lower -priority than <>. In order to circumvent this, the user -only needs to specialize <> to not match `ip_address`. +fit, though. The only problem is that `tuple_category` has a lower priority +than `sequence_category`. In order to circumvent this, the user only needs to +specialize `use_category` for `ip_address`. [source] ---- @@ -84,7 +89,7 @@ include::../../../test/snippets.cpp[tag=snippet_tag_invoke_1,indent=0] ---- Since the type being converted is embedded into the function's signature, -user-provided overloads are visible to argument-dependent lookup and will be +user-provided overloads are visible to argument-dependent lookup and become candidates when a conversion is performed: [source] diff --git a/doc/pages/conversion/direct.adoc b/doc/pages/conversion/direct.adoc index 4e986574e..f0e3e3d6c 100644 --- a/doc/pages/conversion/direct.adoc +++ b/doc/pages/conversion/direct.adoc @@ -18,7 +18,7 @@ The drawback of this approach is that fully custom type representations are not supported, only the library-provided conversions are. Also all objects that should be populated by parsing have to be default constructible types. This includes not only the top-level object, but e.g. elements of containers, -members of described `struct`s, and alternatives of variants. +members of described ``struct``s, and alternatives of variants. That being said, if your types are default-constructible and you don't need the customisability allowed by <> and <>, then you diff --git a/doc/pages/conversion/nothrow.adoc b/doc/pages/conversion/nothrow.adoc index c73d17af8..38d95d626 100644 --- a/doc/pages/conversion/nothrow.adoc +++ b/doc/pages/conversion/nothrow.adoc @@ -23,7 +23,7 @@ it supports with <>. If a user wants to use it with custom types, an overload of `tag_invoke` of this form needs to be provided: ``` -result_for::type +boost::system::result tag_invoke( const try_value_to_tag< T >&, const value& ); ``` diff --git a/doc/pages/conversion/overview.adoc b/doc/pages/conversion/overview.adoc index f11b5fd64..317229694 100644 --- a/doc/pages/conversion/overview.adoc +++ b/doc/pages/conversion/overview.adoc @@ -15,32 +15,28 @@ While the <> container makes it easy to create ad-hoc structures, often it is necessary to convert between JSON and user-defined types or types from the standard library. -The function template <> provides an interface to construct -a <> from a type `T`. The function template <> -converts in the opposite direction, from a type `T` to <>. Both -support a wide variety of different +Boost.JSON provides several components to achieve this. For example, the +function template <> provides an interface to construct +a <> from a type `T`, and the function template <> +converts in the opposite direction, from a type `T` to <>. These +components support a wide variety of different https://en.cppreference.com/w/cpp/language/types[fundamental types], such as -`int` or `double`, standard library types, such as `std::string` or -`std::vector`, and can be extended to support user-defined types. +`int` or `double`, standard library types, such as {std_string} or +{std_vector}, and can be extended to support user-defined types. [source] ---- include::../../../test/snippets.cpp[tag=snippet_conv_1,indent=0] ---- -For the type `T`, the appropriate conversion approach is chosen from the -following list of categories. The first matching category is selected. +For a type `T` the library chooses a conversion implementation that corresponds +to the first matching category from the following list. .Conversion categories [%autowidth,cols=4] |=== |Category of T|Comment|`value_from` behavior|`value_to` behavior -|Custom conversion. -| -|Custom behavior. -|Custom behavior. - |Boost.JSON container. | |The result is equal to the input value. @@ -55,57 +51,57 @@ following list of categories. The first matching category is selected. | a| The result is a number equal to input and has the type -* `std::int64_t`, if `T` is a signed integer'; or +* `std::int64_t`, if `T` is a signed integer; or * `std::uint64_t`, if `T` is an unsigned integer; or * `double` otherwise. |The result is created via <>. -|Type satisfying <> +|<> |Intended for types like {std_monostate}. |The result is a null value. |The result is default-constructed. -|Type satisfying <>. -|A sequence of `char`s, e.g. `std::string`. +|<> +|A sequence of ``char``s, e.g. {std_string}. |The result is a <>. |The result is constructed from a <>. -|Type satisfying <>. -|`std::variant` and similar types, e.g. `boost::variant2::variant`. +|<> +|{std_variant} and similar types, e.g. {ref_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 <> -| +|<> +|{std_optional} and similar types, e.g. {ref_optional}. |If the input value is empty, the result is a `null`. Otherwise it is equivalent to conversion of the object stored inside of optional. |The result is default constructed if the input value is `null`. Otherwise the result is constructed from the result of conversion of the input to the type stored in optional. -|Type satisfying <>. -|A one-to-one mapping (e.g. `std::map`) with string-like keys. +|<> +|A one-to-one mapping (e.g. {std_map}) with string-like keys. |The result is an <>. |The result is default-constructed, and elements are `insert`-ed at the end. -|Type satisfying <>. -|A sequence of elements, e.g. `std::vector`. +|<> +|A sequence of elements, e.g. {std_vector}. |The result is an <>. |The result is default-constructed, and elements are `insert`-ed at the end. -|Type satisfying <>. -|A heterogenous sequence with fixed size, e.g. `std::tuple` and `std::pair`. +|<> +|A heterogenous sequence with fixed size, e.g. {std_tuple} and {std_pair}. |The result is an <>. |The result is constructed with the array elements as constructor arguments. -|Type satisfying <> +|<> | |The result is an <> with described members' names as keys. |The result is default-constructed and described members are assigned corresponding values. -|Type satisfying <> +|<> | |If the input value is equal to one of the described enumerators, the result is a <>, containing its name. Otherwise it's equal to the input @@ -113,8 +109,8 @@ a| The result is a number equal to input and has the type |The result is the described enumerator, corresponding to the input <>. -|Type satisfying <>. -|`std::filesystem::path` and similar types, e.g. `boost::filesystem::path`. +|<> +|{std_path} and similar types, e.g. {ref_path}. |The result is equal to the result of `path::generic_string`. |The result is constructed from two pointers to `const char`. |=== @@ -127,11 +123,11 @@ contained objects is applied recursively. For example: include::../../../test/snippets.cpp[tag=snippet_conv_recursive,indent=0] ---- -Here, the map is converted into an <>, since it matches -<>. Each of its keys is converted into a <>, as -`std::string` matches <>, and each of its values is -converted into an <>, as `std::pair` matches <>. -Finally, elements of pairs are converted into a `std::int64_t` number and +Here, the map is converted to an <>, since its conversion category +is <>. Each of its keys is converted into a <>, +as `std::string` is of <>, and each of its values is +converted into an <>, as `std::pair` is of <>. +Finally, elements of pairs are converted to a `std::int64_t` number and a `bool`. :leveloffset: +1 @@ -140,8 +136,8 @@ include::custom.adoc[] include::nothrow.adoc[] include::alloc.adoc[] include::context.adoc[] -include::forward.adoc[] include::direct.adoc[] +include::forward.adoc[] include::guidelines.adoc[] :leveloffset: -1 diff --git a/doc/pages/definitions.adoc b/doc/pages/definitions.adoc index 56d6e4d3a..2dd31255d 100644 --- a/doc/pages/definitions.adoc +++ b/doc/pages/definitions.adoc @@ -12,8 +12,11 @@ Official repository: https://github.com/boostorg/json :ref_error_category: pass:q[https://boost.org/doc/libs/latest/libs/system/doc/html/system.html#ref_error_category[`error_category`]] :ref_error_code: pass:q[https://boost.org/doc/libs/latest/libs/system/doc/html/system.html#ref_error_code[`error_code`]] :ref_error_condition: pass:q[https://boost.org/doc/libs/latest/libs/system/doc/html/system.html#ref_error_condition[`error_condition`]] +:ref_optional: pass:q[https://www.boost.org/doc/libs/latest/libs/optional/doc/html/boost_optional/reference/header__boost_optional_optional_hpp_/header_optional_optional_values.html#reference_operator_template[`boost::optional`]] +:ref_path: pass:q[https://www.boost.org/doc/libs/latest/libs/filesystem/doc/reference.html#class-path[`boost::filesystem::path`]] :ref_result: pass:q[https://boost.org/doc/libs/latest/libs/system/doc/html/system.html#ref_resultt_e[`result`]] :ref_system_error: pass:q[https://boost.org/doc/libs/latest/libs/system/doc/html/system.html#ref_system_error[`system_error`]] +:ref_variant: pass:q[https://www.boost.org/doc/libs/latest/libs/variant2/doc/html/variant2.html#ref_variant[`boost::variant2::variant`]] :req_Allocator: pass:q[https://en.cppreference.com/w/cpp/named_req/Allocator[__Allocator__]] :req_CopyAssignable: pass:q[https://en.cppreference.com/w/cpp/named_req/CopyAssignable[__CopyAssignable__]] @@ -33,12 +36,17 @@ Official repository: https://github.com/boostorg/json :std_initializer_list: pass:q[https://en.cppreference.com/w/cpp/utility/initializer_list[`std::initializer_list`]] :std_complex: pass:q[https://en.cppreference.com/w/cpp/numeric/complex[`std::complex`]] :std_hash: pass:q[https://en.cppreference.com/w/cpp/utility/hash[`std::hash`]] +:std_map: pass:q[https://en.cppreference.com/w/cpp/container/map[`std::map`]] :std_memory_resource: pass:q[https://en.cppreference.com/w/cpp/memory/memory_resource[`std::pmr::memory_resource`]] :std_monostate: pass:q[https://en.cppreference.com/w/cpp/utility/variant/monostate[`std::monostate`]] +:std_optional: pass:q[https://en.cppreference.com/w/cpp/utility/optional[`std::optional`]] :std_ostream: pass:q[https://en.cppreference.com/w/cpp/io/basic_ostream[`std::ostream`]] +:std_pair: pass:q[https://en.cppreference.com/w/cpp/utility/pair[`std::pair`]] +:std_path: pass:q[https://en.cppreference.com/w/cpp/filesystem/path[`std::filesystem::path`]] :std_polymorphic_allocator: pass:q[https://en.cppreference.com/w/cpp/memory/polymorphic_allocator[`std::pmr::polymorphic_allocator`]] :std_string: pass:q[https://en.cppreference.com/w/cpp/string/basic_string[`std::string`]] :std_unordered_map: pass:q[https://en.cppreference.com/w/cpp/container/unordered_map[`std::unordered_map`]] :std_uses_allocator: pass:q[https://en.cppreference.com/w/cpp/memory/uses_allocator[`std::uses_allocator`]] +:std_variant: pass:q[https://en.cppreference.com/w/cpp/utility/variant[`std::variant`]] :std_vector: pass:q[https://en.cppreference.com/w/cpp/container/vector[`std::vector`]] :std_tuple: pass:q[https://en.cppreference.com/w/cpp/utility/tuple[`std::tuple`]] diff --git a/doc/pages/reference.adoc b/doc/pages/reference.adoc index 95849d615..629c48ac1 100644 --- a/doc/pages/reference.adoc +++ b/doc/pages/reference.adoc @@ -33,6 +33,10 @@ a| *Classes* + <> + <> +*Aliases* + +<> + +<> + a| *Functions* + <> + <> + @@ -52,7 +56,7 @@ a| *Functions* + <> + <> -| *Operators* + +a| *Operators* + <> + <> + <> + @@ -61,37 +65,53 @@ a| *Functions* + <> + <> + -*Aliases* + -<> + -<> - *Constants* + <> + <> + +<> + <> + <> + <> + <> + <> -| *Type Traits* + +a| *Type Traits* + +<> + +<> + <> + <> + <> + +<> + +<> + +<> + +<> + +<> + +<> + +<> + +<> + +<> + +<> + +<> + +<> + +<> + +<> + +4+h|Deprecated + +a| <> + <> + -<> + +<> +a| <> + -<> + +<> +a| <> + <> + -<> + +<> +a| <> + -<> + -<> + -<> + -<> + -<> +<> |=== diff --git a/include/boost/json/conversion.hpp b/include/boost/json/conversion.hpp index 5774e2b50..94d2ea806 100644 --- a/include/boost/json/conversion.hpp +++ b/include/boost/json/conversion.hpp @@ -12,6 +12,7 @@ #include #include +#include #include @@ -25,6 +26,59 @@ struct supported_context; } // namespace detail +/** Conversion categories supported by the library. + + With the exception of `unknown` every enumerator of this enum represents a + conversion category supported by this library. These categories are used + to pick appropriate implementations of @ref value_to, @ref try_value_to, + @ref value_from, @ref parse_into, and @ref serialize. + + `unknown` category is assigned to types for which the library could not + deduce an appropriate category. Using the aforementioned functions with + objects of such types will result in compilation failure. + */ +enum class conversion_category +{ + /// See @ref unknown_category + unknown, + /// See @ref null_category + null, + /// See @ref string_category + string, + /// See @ref variant_category + variant, + /// See @ref optional_category + optional, + /// See @ref map_category + map, + /// See @ref sequence_category + sequence, + /// See @ref tuple_category + tuple, + /// See @ref described_class_category + described_class, + /// See @ref described_enum_category + described_enum, + /// See @ref path_category + path, + + // internal categories +#ifndef BOOST_JSON_DOCS + boolean, + integer, + floating_point, + json_value, + json_object, + json_array, + json_string, + json_value_ref, + + user, + user_context, + user_full_context, +#endif +}; + /** Customization point tag. This tag type is used by the function @ref value_from to select overloads @@ -68,6 +122,9 @@ struct try_value_to_tag { }; /** Determine if `T` can be treated like a string during conversions. + @warning This trait is deprecated and **will be removed in Boost 1.93.0.** + Switch to using @ref use_category and @ref string_category. + Provides the member constant `value` that is equal to `true`, if `T` is convertible to @ref string_view. Otherwise, `value` is equal to `false`. @@ -94,8 +151,37 @@ struct try_value_to_tag { }; template struct is_string_like; +/** Conversion category constant for strings. + + String types are represented as JSON strings. + + By default a type is considered a string if it is convertible to @ref + string_view. + + Users can specialize the trait for their own types if they want them to be + treated like strings. For example: + + @code + namespace boost { namespace json { + + template <> + struct is_string_like : string_category + { }; + + } } + @endcode + + @par Matching Types + @ref string_view, @ref std::string, @ref std::string_view. +*/ +using string_category = std::integral_constant< + conversion_category, conversion_category::string>; + /** Determine if `T` can be treated like `std::filesystem::path` during conversions. + @warning This trait is deprecated and **will be removed in Boost 1.93.0.** + Switch to @ref use_category and @ref path_category. + Given `t`, a glvalue of type `T`, if - given `It`, the type denoted by `decltype(std::begin(t))`, @@ -135,8 +221,49 @@ struct is_string_like; template struct is_path_like; +/** Conversion category constant for filesystem paths. + + Paths are represented in JSON as strings. + + By default a type `T` is considered a path if + + - given `t`, a glvalue of type `T`; and + + - given `It`, the type denoted by `decltype(std::begin(t))`, + `std::iterator_traits::iterator_category` is well-formed and denotes + a type; and + + - `std::iterator_traits::value_type` is `T`; and + + - `T::value_type` is well-formed and denotes a type; and + + - `T::string_type` is well-formed, denotes a type, and is an alias for + `std::basic_string< T::value_type >`. + + Users can specialize @ref use_category for their own types if they want + them to be treated like filesystem paths. For example: + + @code + namespace boost { namespace json { + + template <> + struct use_category : path_category + { }; + + } } + @endcode + + @par Matching Types + @ref std::filesystem::path, @ref boost::filesystem::path. +*/ +using path_category = std::integral_constant< + conversion_category, conversion_category::path>; + /** Determine if `T` can be treated like a sequence during conversions. + @warning This trait is deprecated and **will be removed in Boost 1.93.0.** + Switch to using @ref use_category and @ref sequence_category. + Given `t`, a glvalue of type `T`, if - given `It`, the type denoted by `decltype(std::begin(t))`, @@ -145,7 +272,7 @@ struct is_path_like; - `decltype(std::end(t))` also denotes the type `It`; and - - `std::iterator_traits::value_type` is not `T`; and + - `std::iterator_traits::value_type` is not `T`. then the trait provides the member constant `value` that is equal to `true`. Otherwise, `value` is equal to `false`. @@ -174,9 +301,49 @@ struct is_path_like; template struct is_sequence_like; +/** Conversion category constant for sequences. + + Sequences are represented in JSON as arrays. + + By default a type `T` is considered a sequence if + + - given `t`, a glvalue of type `T`; and + + - given `It`, the type denoted by `decltype(std::begin(t))`, + `std::iterator_traits::iterator_category` is well-formed and denotes + a type; and + + - `decltype(std::end(t))` also denotes the type `It`; and + + - `std::iterator_traits::value_type` is not `T`; and + + - `std::iterator_traits::value_type` is not `T`. + + Users can specialize @ref use_category for their own types if they want + them to be treated like sequences. For example: + + @code + namespace boost { namespace json { + + template <> + struct use_category : sequence_category + { }; + + } } + @endcode + + @par Matching Types + Any {req_SequenceContainer}, array types. +*/ +using sequence_category = std::integral_constant< + conversion_category, conversion_category::sequence>; + /** Determine if `T` can be treated like a 1-to-1 mapping during conversions. + @warning This trait is deprecated and **will be removed in Boost 1.93.0.** + Switch to using @ref use_category and @ref map_category. + Given `t`, a glvalue of type `T`, if - `is_sequence_like::value` is `true`; and @@ -219,8 +386,62 @@ struct is_sequence_like; template struct is_map_like; +/** Conversion category constant for maps. + + Maps are represented in JSON as objects. Such representation limits this + category to 1-to-1 maps (as opposed to 1-to-many e.g. @ref std::multimap) + with string keys. + + By default a type `T` is considered a map if + + - given `t`, a glvalue of type `T`; and + + - given `It`, the type denoted by `decltype(std::begin(t))`, + `std::iterator_traits::iterator_category` is well-formed and denotes + a type; and + + - `decltype(std::end(t))` also denotes the type `It`; and + + - `std::iterator_traits::value_type` is not `T`; and + + - given types `K` and `M`, `std::iterator_traits::value_type` denotes + `std::pair`; and + + - conversion category of K is @ref conversion_category::string; and + + - given `v`, a value of type `std::iterator_traits::value_type`, + `std::tuple_size< decltype(t.emplace(v)) >::value` is a positive number. + + Less formally, `T` should be a sequence of `std::pair`s with unique + string-like keys. + + @note The restriction for `t.emplace()` return type ensures that the + container does not allow duplicate keys. + + Users can specialize @ref use_category for their own types if they want + them to be treated like maps. For example: + + @code + namespace boost { namespace json { + + template <> + struct is_map_like : map_category + { }; + + } } + @endcode + + @par Matching Types + @ref std::map, @ref std::unordered_map. +*/ +using map_category = std::integral_constant< + conversion_category, conversion_category::map>; + /** Determine if `T` can be treated like a tuple during conversions. + @warning This trait is deprecated and **will be removed in Boost 1.93.0.** + Switch to using @ref use_category and @ref tuple_category. + Provides the member constant `value` that is equal to `true`, if `std::tuple_size::value` is a positive number. Otherwise, `value` is equal to `false`. @@ -249,8 +470,38 @@ struct is_map_like; template struct is_tuple_like; +/** Conversion category constant for tuples. + + Tuples are represented in JSON as arrays. + + By default a type `T` is considered a tuple if `std::tuple_size::value` + is a positive number. + + Users can specialize @ref use_category for their own types if they want + them to be treated like tuples. For example: + + @code + namespace boost { namespace json { + + template <> + struct use_category : tuple_category + { }; + + } } + @endcode + + + @par Matching Types + @ref std::tuple, @ref std::pair. +*/ +using tuple_category = std::integral_constant< + conversion_category, conversion_category::tuple>; + /** Determine if `T` can be treated like null during conversions. + @warning This trait is deprecated and **will be removed in Boost 1.93.0.** + Switch to using @ref use_category and @ref null_category. + Primary template instantiations provide the member constant `value` that is equal to `false`. Users can specialize the trait for their own types if they **do** want them to be treated as nulls. For example: @@ -277,9 +528,38 @@ struct is_null_like : std::false_type { }; +/** Conversion category constant for null types. + + Null types are represented in JSON as the `null` literal. + + By default only the types @ref std::nullptr_t and @ref std::monostate are + considered null types. + + Users can specialize @ref use_category for their own types if they want + them to be treated as nulls. For example: + + @code + namespace boost { namespace json { + + template <> + struct use_category : null_category + { }; + + } } + @endcode + + @par Matching Types + @ref std::nullptr_t, @ref std::monostate. +*/ +using null_category = std::integral_constant< + conversion_category, conversion_category::null>; + /** Determine if `T` should be treated as a described class. - Described classes are serialised as objects with an element for each + @warning This trait is deprecated and **will be removed in Boost 1.93.0.** + Switch to @ref use_category and @ref described_class_category. + + Described classes are serialized as objects with an element for each described data member. Described bases are serialized in a flattened way, that is members of bases are serialized as direct elements of the object, and no nested objects are created for bases. @@ -327,9 +607,58 @@ struct is_null_like template struct is_described_class; +/** Conversion category constant for described classes. + + Described classes are represented in JSON as objects with an element for + each described data member. Described bases are flattened, that is members + of bases are represented as direct elements of the object rather than + elements of some subobjects. + + By default a described class should not have non-public described members + (including inherited members) or non-public non-empty described bases. Or + more formally a type `T` is considered a described class if + + - `boost::describe::has_describe_members::value` is `true`; and + + - given `L`, a class template of the form `template struct L {}`, + `boost::describe::describe_members` denotes + `L<>`; and + + - `std::is_union::value` is `false`. + + @note Shadowed members are ignored both for requirements checking and for + performing conversions. + + Users can specialize @ref use_category for their own types if they want + them to be treated as described classes. For example: + + @code + namespace boost { namespace json { + + template <> + struct use_category + : described_class_category + { }; + + } } + @endcode + + Users can also specialize the trait for their own types _with_ described + non-public data members to enable this conversion implementation. Note that + non-public bases are not supported regardless. + + @see [Boost.Describe](https://www.boost.org/doc/libs/latest/libs/describe/doc/html/describe.html). +*/ +using described_class_category = std::integral_constant< + conversion_category, conversion_category::described_class>; + /** Determine if `T` should be treated as a described enum. - Described enums are serialised as strings when their value equals to a + @warning This trait is deprecated and **will be removed in Boost 1.93.0.** + Switch to @ref use_category and @ref described_enum_category. + + Described enums are serialized as strings when their value equals to a described enumerator, and as integers otherwise. The reverse operation does not convert numbers to enums values, though, and instead produces an error. @@ -358,10 +687,42 @@ struct is_described_class; template struct is_described_enum; +/** Conversion category constant for described enums. + + Described enums are serialized as strings when their value equals to a + described enumerator, and as integers otherwise. The reverse operation + does not convert numbers to enums values, though, and instead produces + an error. + + By default a type `T` is considered a described enum if + `boost::describe::has_describe_enumerators::value` is `true`. + + Users can specialize @ref use_category for their own enums if they want + them to be treated as described enums. For example: + + @code + namespace boost { namespace json { + + template <> + struct use_category + : described_enum_category + { }; + + } } + @endcode + + @see [Boost.Describe](https://www.boost.org/doc/libs/latest/libs/describe/doc/html/describe.html). +*/ +using described_enum_category = std::integral_constant< + conversion_category, conversion_category::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 + @warning This trait is deprecated and **will be removed in Boost 1.93.0.** + Switch to using @ref use_category and @ref variant_category. + + Variants are serialized the same way their active alternative is + serialized. 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 @@ -389,9 +750,40 @@ struct is_described_enum; template struct is_variant_like; +/** Conversion category constant for variants. + + Variants are serialized the same way their active alternative is + serialized. The opposite conversion selects the first alternative for which + conversion succeeds. + + By default a type `T` is considered a variant if given `t`, a glvalue of + type `const T`, `t.valueless_by_exception()` is well-formed. + + Users can specialize @ref use_category for their own types if they want + them to be treated as variants. For example: + + @code + namespace boost { namespace json { + + template <> + struct use_category : variant_category + { }; + + } } + @endcode + + @par Mathcing Types + @ref std::variant, @ref boost::variant2::variant. +*/ +using variant_category = std::integral_constant< + conversion_category, conversion_category::variant>; + /** Determine if `T` should be treated as an optional - Optionals are serialised as `null` if empty, or as the stored type + @warning This trait is deprecated and **will be removed in Boost 1.93.0.** + Switch to using @ref use_category and @ref optional_category. + + Optionals are serialized as `null` if empty, or as the stored type otherwise. Given `t`, a glvalue of type `T`, if @@ -423,6 +815,77 @@ struct is_variant_like; template struct is_optional_like; +/** Conversion category constant for optionals. + + Optionals are represented in JSON as `null` if not engaged (i.e. does not + store a value), or as the stored type otherwise. + + By default a type `T` is considered an optional if + + - given `t`, a glvalue of type `T`, `decltype( t.value() )` is well-formed + and isn't a void type; and + - `t.reset()` is well-formed; + + Users can specialize @ref use_category for their own types if they want + them to be treated as optionals. For example: + + @code + namespace boost { namespace json { + + template <> + struct use_category : optional_category + { }; + + } } + @endcode + + @par Matching Types + @ref std::optional, @ref boost::optional. +*/ +using optional_category = std::integral_constant< + conversion_category, conversion_category::optional>; + +/** Unknown conversion category. + + This category is assigned by the library's conversion category deduction + mechanism when it fails. +*/ +using unknown_category = std::integral_constant< + conversion_category, conversion_category::unknown>; + +/** Overrides the category of conversion of a type. + + Specializations should provide the static member constant `value` that is + equal to an enumerator of @ref conversion_category that represents the + suitable conversion category of T in context `Ctx`. + + The primary template's `value` is equal to @ref + conversion_category::unknown. + + Use `void` for `Ctx` (the default) if the category should apply regardless + of context. + + The third template parameter can be used for conditional specializations. + + Usage example: + + @code + namespace boost { + namespace json { + + template <> + struct conversion_category : string_category + { }; + + } // namespace boost + } // namespace json + @endcode + + @see \<\>, @ref value_from, @ref value_to. +*/ +template +struct use_category; + } // namespace json } // namespace boost diff --git a/include/boost/json/detail/parse_into.hpp b/include/boost/json/detail/parse_into.hpp index 003c8d3d1..d56d98ed2 100644 --- a/include/boost/json/detail/parse_into.hpp +++ b/include/boost/json/detail/parse_into.hpp @@ -36,9 +36,9 @@ * handler (in this case, it's the top handler, into_handler). The type is * actually an alias to class template converting_handler, which has a separate * specialisation for every conversion category from the list of generic - * conversion categories (e.g. sequence_conversion_tag, tuple_conversion_tag, - * etc.) Instantiations of the template store a pointer to the parent handler - * and a pointer to the value T. + * conversion categories (e.g. sequence_category, tuple_category, etc.) + * Instantiations of the template store a pointer to the parent handler and a + * pointer to the value T. * * The nested handler handles specific parser events by setting error_code to * an appropriate value, if it receives an event it isn't supposed to handle @@ -75,12 +75,16 @@ namespace boost { namespace json { namespace detail { -template< class Impl, class T, class Parent > +template< conversion_category C, class T, class Parent > class converting_handler; // get_handler template< class V, class P > -using get_handler = converting_handler< generic_conversion_category, V, P >; +using get_handler = converting_handler< + get_conversion_category< + V, void, direct_custom_checks, direct_fallback_checks>::value, + V, + P>; template class handler_error_base { @@ -268,7 +272,7 @@ bool integral_in_range( std::uint64_t v ) } template< class V, class P > -class converting_handler +class converting_handler : public scalar_handler { private: @@ -312,7 +316,7 @@ class converting_handler // floating point handler template< class V, class P> -class converting_handler +class converting_handler : public scalar_handler { private: @@ -350,7 +354,7 @@ class converting_handler // string handler template< class V, class P > -class converting_handler +class converting_handler : public scalar_handler { private: @@ -389,7 +393,7 @@ class converting_handler // bool handler template< class V, class P > -class converting_handler +class converting_handler : public scalar_handler { private: @@ -410,7 +414,7 @@ class converting_handler // null handler template< class V, class P > -class converting_handler +class converting_handler : public scalar_handler { private: @@ -431,7 +435,7 @@ class converting_handler // described enum handler template< class V, class P > -class converting_handler +class converting_handler : public scalar_handler { #ifndef BOOST_DESCRIBE_CXX14 @@ -479,7 +483,7 @@ class converting_handler }; template< class V, class P > -class converting_handler +class converting_handler { static_assert( sizeof(V) == 0, "This type is not supported" ); }; @@ -536,9 +540,9 @@ clear_container( } template< class V, class P > -class converting_handler +class converting_handler : public composite_handler< - converting_handler, + converting_handler, detail::value_type, P, error::not_array> @@ -611,9 +615,9 @@ class converting_handler // map handler template< class V, class P > -class converting_handler +class converting_handler : public composite_handler< - converting_handler, + converting_handler, detail::mapped_type, P, error::not_object> @@ -800,7 +804,7 @@ struct tuple_accessor }; template< class T, class P > -class converting_handler +class converting_handler { private: @@ -1121,7 +1125,7 @@ struct ignoring_handler }; template -class converting_handler +class converting_handler { #if !defined(BOOST_DESCRIBE_CXX14) @@ -1466,7 +1470,7 @@ using inner_handler_variant = mp11::mp_push_front< variant2::monostate>; template< class T, class P > -class converting_handler +class converting_handler { private: using variant_size = mp11::mp_size; @@ -1663,7 +1667,7 @@ class converting_handler // optional handler template -class converting_handler +class converting_handler { private: using inner_type = value_result_type; @@ -1788,7 +1792,7 @@ class converting_handler // path handler template< class V, class P > -class converting_handler +class converting_handler : public scalar_handler { private: diff --git a/include/boost/json/detail/value_from.hpp b/include/boost/json/detail/value_from.hpp index 122e398a7..e5c8a91c8 100644 --- a/include/boost/json/detail/value_from.hpp +++ b/include/boost/json/detail/value_from.hpp @@ -47,14 +47,14 @@ struct append_tuple_element { template< class T, class Ctx > void -value_from_impl( user_conversion_tag, value& jv, T&& from, Ctx const& ) +value_from_impl( user_category, value& jv, T&& from, Ctx const& ) { 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) +value_from_impl( user_context_category, value& jv, T&& from, Ctx const& ctx) { using Sup = supported_context; tag_invoke( value_from_tag(), jv, static_cast(from), Sup::get(ctx) ); @@ -63,7 +63,7 @@ value_from_impl( context_conversion_tag, value& jv, T&& from, Ctx const& ctx) template< class T, class Ctx > void value_from_impl( - full_context_conversion_tag, value& jv, T&& from, Ctx const& ctx) + user_full_context_category, value& jv, T&& from, Ctx const& ctx) { using Sup = supported_context; tag_invoke( @@ -73,9 +73,14 @@ value_from_impl( //---------------------------------------------------------- // Native conversion -template< class T, class Ctx > +template< class Cat, class T, class Ctx > void -value_from_impl( native_conversion_tag, value& jv, T&& from, Ctx const& ) +value_from_impl( + Cat, + value& jv, + T&& from, + Ctx const&, + typename std::enable_if::value>* = nullptr) { jv = std::forward(from); } @@ -83,7 +88,7 @@ value_from_impl( native_conversion_tag, value& jv, T&& from, Ctx const& ) // null-like types template< class T, class Ctx > void -value_from_impl( null_like_conversion_tag, value& jv, T&&, Ctx const& ) +value_from_impl( null_category, value& jv, T&&, Ctx const& ) { // do nothing BOOST_ASSERT(jv.is_null()); @@ -93,7 +98,7 @@ value_from_impl( null_like_conversion_tag, value& jv, T&&, Ctx const& ) // string-like types template< class T, class Ctx > void -value_from_impl( string_like_conversion_tag, value& jv, T&& from, Ctx const& ) +value_from_impl( string_category, value& jv, T&& from, Ctx const& ) { auto sv = static_cast(from); jv.emplace_string().assign(sv); @@ -102,7 +107,7 @@ value_from_impl( string_like_conversion_tag, value& jv, T&& from, Ctx const& ) // map-like types template< class T, class Ctx > void -value_from_impl( map_like_conversion_tag, value& jv, T&& from, Ctx const& ctx ) +value_from_impl( map_category, value& jv, T&& from, Ctx const& ctx ) { using std::get; object& obj = jv.emplace_object(); @@ -116,7 +121,7 @@ value_from_impl( map_like_conversion_tag, value& jv, T&& from, Ctx const& ctx ) // ranges template< class T, class Ctx > void -value_from_impl( sequence_conversion_tag, value& jv, T&& from, Ctx const& ctx ) +value_from_impl( sequence_category, value& jv, T&& from, Ctx const& ctx ) { array& result = jv.emplace_array(); result.reserve(detail::try_size(from, size_implementation())); @@ -133,7 +138,7 @@ value_from_impl( sequence_conversion_tag, value& jv, T&& from, Ctx const& ctx ) // tuple-like types template< class T, class Ctx > void -value_from_impl( tuple_conversion_tag, value& jv, T&& from, Ctx const& ctx ) +value_from_impl( tuple_category, value& jv, T&& from, Ctx const& ctx ) { constexpr std::size_t n = std::tuple_size>::value; @@ -146,7 +151,7 @@ value_from_impl( tuple_conversion_tag, value& jv, T&& from, Ctx const& ctx ) // no suitable conversion implementation template< class T, class Ctx > void -value_from_impl( no_conversion_tag, value&, T&&, Ctx const& ) +value_from_impl( unknown_category, value&, T&&, Ctx const& ) { static_assert( !std::is_same::value, @@ -184,7 +189,7 @@ struct from_described_member template< class T, class Ctx > void value_from_impl( - described_class_conversion_tag, value& jv, T&& from, Ctx const& ctx ) + described_class_category, value& jv, T&& from, Ctx const& ctx ) { object& obj = jv.emplace_object(); from_described_member member_converter{ @@ -199,8 +204,7 @@ value_from_impl( // described enums template< class T, class Ctx > void -value_from_impl( - described_enum_conversion_tag, value& jv, T from, Ctx const& ) +value_from_impl( described_enum_category, value& jv, T from, Ctx const& ) { (void)jv; (void)from; @@ -222,8 +226,7 @@ value_from_impl( // optionals template< class T, class Ctx > void -value_from_impl( - optional_conversion_tag, value& jv, T&& from, Ctx const& ctx ) +value_from_impl( optional_category, value& jv, T&& from, Ctx const& ctx ) { if( from ) value_from( *from, ctx, jv ); @@ -248,14 +251,14 @@ struct value_from_visitor template< class Ctx, class T > void -value_from_impl( variant_conversion_tag, value& jv, T&& from, Ctx const& ctx ) +value_from_impl( variant_category, value& jv, T&& from, Ctx const& ctx ) { visit( value_from_visitor{ jv, ctx }, static_cast(from) ); } template< class Ctx, class T > void -value_from_impl( path_conversion_tag, value& jv, T&& from, Ctx const& ) +value_from_impl( path_category, value& jv, T&& from, Ctx const& ) { std::string s = from.generic_string(); string_view sv = s; @@ -266,8 +269,11 @@ value_from_impl( path_conversion_tag, value& jv, T&& from, Ctx const& ) // Contextual conversions template< class Ctx, class T > -using value_from_category = conversion_category< - Ctx, T, value_from_conversion >; +using value_from_category = get_conversion_category< + T, + Ctx, + all_custom_checks::fn, + all_fallback_checks::fn>; } // detail diff --git a/include/boost/json/detail/value_to.hpp b/include/boost/json/detail/value_to.hpp index 66c3858c7..14a44bf6c 100644 --- a/include/boost/json/detail/value_to.hpp +++ b/include/boost/json/detail/value_to.hpp @@ -76,7 +76,7 @@ try_reserve( template< class Ctx > system::result value_to_impl( - value_conversion_tag, + json_value_category, try_value_to_tag, value const& jv, Ctx const& ) @@ -87,7 +87,7 @@ value_to_impl( template< class Ctx > value value_to_impl( - value_conversion_tag, value_to_tag, value const& jv, Ctx const& ) + json_value_category, value_to_tag, value const& jv, Ctx const& ) { return jv; } @@ -96,7 +96,7 @@ value_to_impl( template< class Ctx > system::result value_to_impl( - object_conversion_tag, + json_object_category, try_value_to_tag, value const& jv, Ctx const& ) @@ -113,7 +113,7 @@ value_to_impl( template< class Ctx > system::result value_to_impl( - array_conversion_tag, + json_array_category, try_value_to_tag, value const& jv, Ctx const& ) @@ -130,7 +130,7 @@ value_to_impl( template< class Ctx > system::result value_to_impl( - string_conversion_tag, + json_string_category, try_value_to_tag, value const& jv, Ctx const& ) @@ -147,7 +147,7 @@ value_to_impl( template< class Ctx > system::result value_to_impl( - bool_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) + boolean_category, try_value_to_tag, value const& jv, Ctx const& ) { auto b = jv.if_bool(); if( b ) @@ -157,11 +157,17 @@ value_to_impl( return {boost::system::in_place_error, ec}; } -// integral and floating point -template< class T, class Ctx > +template< class Cat, class T, class Ctx > system::result value_to_impl( - number_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) + Cat, + try_value_to_tag, + value const& jv, + Ctx const&, + typename std::enable_if< + Cat::value == conversion_category::integer + || Cat::value == conversion_category::floating_point>::type* = nullptr + ) { system::error_code ec; auto const n = jv.to_number(ec); @@ -174,7 +180,7 @@ value_to_impl( template< class T, class Ctx > system::result value_to_impl( - null_like_conversion_tag, + null_category, try_value_to_tag, value const& jv, Ctx const& ) @@ -190,10 +196,7 @@ value_to_impl( template< class T, class Ctx > system::result value_to_impl( - string_like_conversion_tag, - try_value_to_tag, - value const& jv, - Ctx const& ) + string_category, try_value_to_tag, value const& jv, Ctx const& ) { auto str = jv.if_string(); if( str ) @@ -207,7 +210,7 @@ value_to_impl( template< class T, class Ctx > system::result value_to_impl( - map_like_conversion_tag, + map_category, try_value_to_tag, value const& jv, Ctx const& ctx ) @@ -247,10 +250,7 @@ value_to_impl( template< class T, class Ctx > system::result value_to_impl( - sequence_conversion_tag, - try_value_to_tag, - value const& jv, - Ctx const& ctx ) + sequence_category, try_value_to_tag, value const& jv, Ctx const& ctx ) { array const* arr = jv.if_array(); if( !arr ) @@ -331,10 +331,7 @@ try_make_tuple_like( template< class T, class Ctx > system::result value_to_impl( - tuple_conversion_tag, - try_value_to_tag, - value const& jv, - Ctx const& ctx ) + tuple_category, try_value_to_tag, value const& jv, Ctx const& ctx ) { system::error_code ec; @@ -411,7 +408,7 @@ struct to_described_member template< class T, class Ctx > system::result value_to_impl( - described_class_conversion_tag, + described_class_category, try_value_to_tag, value const& jv, Ctx const& ctx ) @@ -444,7 +441,7 @@ value_to_impl( template< class T, class Ctx > system::result value_to_impl( - described_enum_conversion_tag, + described_enum_category, try_value_to_tag, value const& jv, Ctx const& ) @@ -475,7 +472,7 @@ value_to_impl( template< class T, class Ctx > system::result value_to_impl( - optional_conversion_tag, + optional_category, try_value_to_tag, value const& jv, Ctx const& ctx) @@ -552,7 +549,7 @@ struct alternative_converter template< class T, class Ctx > system::result value_to_impl( - variant_conversion_tag, + variant_category, try_value_to_tag, value const& jv, Ctx const& ctx) @@ -570,7 +567,7 @@ value_to_impl( template< class T, class Ctx > system::result value_to_impl( - path_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) + path_category, try_value_to_tag, value const& jv, Ctx const& ) { auto str = jv.if_string(); if( !str ) @@ -589,7 +586,7 @@ value_to_impl( template< class T, class Ctx > mp11::mp_if< mp11::mp_valid, T > value_to_impl( - user_conversion_tag, value_to_tag tag, value const& jv, Ctx const&) + user_category, value_to_tag tag, value const& jv, Ctx const&) { return tag_invoke(tag, jv); } @@ -602,7 +599,7 @@ template< mp11::mp_if< mp11::mp_valid< has_context_conversion_to_impl, typename Sup::type, T>, T > value_to_impl( - context_conversion_tag, + user_context_category, value_to_tag tag, value const& jv, Ctx const& ctx ) @@ -620,7 +617,7 @@ mp11::mp_if< has_full_context_conversion_to_impl, typename Sup::type, T>, T> value_to_impl( - full_context_conversion_tag, + user_full_context_category, value_to_tag tag, value const& jv, Ctx const& ctx ) @@ -633,7 +630,7 @@ value_to_impl( template< class T, class Ctx > mp11::mp_if_c< !mp11::mp_valid::value, T> value_to_impl( - user_conversion_tag, value_to_tag, value const& jv, Ctx const& ) + user_category, value_to_tag, value const& jv, Ctx const& ) { return tag_invoke(try_value_to_tag(), jv).value(); } @@ -648,7 +645,7 @@ mp11::mp_if_c< has_context_conversion_to_impl, typename Sup::type, T>::value, T> value_to_impl( - context_conversion_tag, value_to_tag, value const& jv, Ctx const& ctx ) + user_context_category, value_to_tag, value const& jv, Ctx const& ctx ) { return tag_invoke( try_value_to_tag(), jv, Sup::get(ctx) ).value(); } @@ -663,7 +660,7 @@ mp11::mp_if_c< has_full_context_conversion_to_impl, typename Sup::type, T>::value, T> value_to_impl( - full_context_conversion_tag, + user_full_context_category, value_to_tag, value const& jv, Ctx const& ctx ) @@ -678,7 +675,7 @@ mp11::mp_if< mp11::mp_valid< has_nonthrowing_user_conversion_to_impl, T>, system::result > value_to_impl( - user_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) + user_category, try_value_to_tag, value const& jv, Ctx const& ) { return tag_invoke(try_value_to_tag(), jv); } @@ -693,7 +690,7 @@ mp11::mp_if< has_nonthrowing_context_conversion_to_impl, typename Sup::type, T>, system::result > value_to_impl( - context_conversion_tag, + user_context_category, try_value_to_tag tag, value const& jv, Ctx const& ctx ) @@ -713,7 +710,7 @@ mp11::mp_if< T>, system::result > value_to_impl( - full_context_conversion_tag, + user_full_context_category, try_value_to_tag tag, value const& jv, Ctx const& ctx ) @@ -759,7 +756,7 @@ mp11::mp_if_c< !mp11::mp_valid::value, system::result > value_to_impl( - user_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) + user_category, try_value_to_tag, value const& jv, Ctx const& ) { return wrap_conversion_exceptions(value_to_tag(), jv); } @@ -776,7 +773,7 @@ mp11::mp_if_c< T>::value, system::result > value_to_impl( - context_conversion_tag, + user_context_category, try_value_to_tag, value const& jv, Ctx const& ctx ) @@ -796,7 +793,7 @@ mp11::mp_if_c< T>::value, system::result > value_to_impl( - full_context_conversion_tag, + user_full_context_category, try_value_to_tag, value const& jv, Ctx const& ctx ) @@ -808,7 +805,7 @@ value_to_impl( // no suitable conversion implementation template< class T, class Ctx > T -value_to_impl( no_conversion_tag, value_to_tag, value const&, Ctx const& ) +value_to_impl( unknown_category, value_to_tag, value const&, Ctx const& ) { static_assert( !std::is_same::value, @@ -824,8 +821,11 @@ value_to_impl( Impl impl, value_to_tag, value const& jv, Ctx const& ctx ) } template< class Ctx, class T > -using value_to_category = conversion_category< - Ctx, T, value_to_conversion >; +using value_to_category = get_conversion_category< + T, + Ctx, + all_custom_checks::fn, + all_fallback_checks::fn>; } // detail diff --git a/include/boost/json/fwd.hpp b/include/boost/json/fwd.hpp index fa6dd7152..79fba9663 100644 --- a/include/boost/json/fwd.hpp +++ b/include/boost/json/fwd.hpp @@ -84,6 +84,9 @@ template typename result_for::type result_from_errno( int e, boost::source_location const* loc ) noexcept; +template +struct use_category; + #endif } // namespace json diff --git a/include/boost/json/impl/conversion.hpp b/include/boost/json/impl/conversion.hpp index 13a460973..60c43fcc5 100644 --- a/include/boost/json/impl/conversion.hpp +++ b/include/boost/json/impl/conversion.hpp @@ -186,33 +186,60 @@ inserter( return std::inserter( target, target.end() ); } +using boolean_category = std::integral_constant< + conversion_category, conversion_category::boolean>; + +using integer_category = std::integral_constant< + conversion_category, conversion_category::integer>; + +using floating_point_category = std::integral_constant< + conversion_category, conversion_category::floating_point>; + +} // namespace detail + + +template +struct use_category : unknown_category {}; + +namespace detail { + 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 { }; -struct array_conversion_tag : native_conversion_tag { }; -struct string_conversion_tag : native_conversion_tag { }; -struct bool_conversion_tag : native_conversion_tag { }; -struct value_ref_tag : native_conversion_tag { }; -struct number_conversion_tag : native_conversion_tag { }; -struct integral_conversion_tag : number_conversion_tag { }; -struct floating_point_conversion_tag : number_conversion_tag { }; -struct null_like_conversion_tag { }; -struct string_like_conversion_tag { }; -struct map_like_conversion_tag { }; -struct path_conversion_tag { }; -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 { }; +using user_category = std::integral_constant< + conversion_category, conversion_category::user>; +using user_context_category = std::integral_constant< + conversion_category, conversion_category::user_context>; +using user_full_context_category = std::integral_constant< + conversion_category, conversion_category::user_full_context>; +using json_value_category = std::integral_constant< + conversion_category, conversion_category::json_value>; +using json_object_category = std::integral_constant< + conversion_category, conversion_category::json_object>; +using json_array_category = std::integral_constant< + conversion_category, conversion_category::json_array>; +using json_string_category = std::integral_constant< + conversion_category, conversion_category::json_string>; +using json_value_ref_category = std::integral_constant< + conversion_category, conversion_category::json_value_ref>; + +template< class Cat > +using is_user_conversion = mp11::mp_bool< + Cat::value == conversion_category::user + || Cat::value == conversion_category::user_context + || Cat::value == conversion_category::user_full_context>; + +template< class Cat > +using is_native_conversion = mp11::mp_bool< + Cat::value == conversion_category::user + || Cat::value == conversion_category::json_value + || Cat::value == conversion_category::json_object + || Cat::value == conversion_category::json_array + || Cat::value == conversion_category::json_string + || Cat::value == conversion_category::json_value_ref + || Cat::value == conversion_category::boolean + || Cat::value == conversion_category::integer + || Cat::value == conversion_category::floating_point>; template using supports_tag_invoke = decltype(tag_invoke( std::declval()... )); @@ -344,109 +371,153 @@ using uniquely_named_members = std::true_type; #endif // BOOST_DESCRIBE_CXX14 // user conversion (via tag_invoke) -template< class Ctx, class T, class Dir > -using user_conversion_category = mp11::mp_cond< - has_user_conversion3, full_context_conversion_tag, - has_user_conversion2, context_conversion_tag, - has_user_conversion1, user_conversion_tag>; +template< class T, class Ctx, class Dir > +using tag_invoke_with_context_category = mp11::mp_cond< + has_user_conversion3, user_full_context_category, + has_user_conversion2, user_context_category>; + +template< class T, class Dir > +using tag_invoke_category = mp11::mp_cond< + has_user_conversion1, user_category>; // 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>; + std::is_same, json_value_ref_category, + std::is_same, json_value_category, + std::is_same, json_array_category, + std::is_same, json_object_category, + std::is_same, json_string_category>; + +template +struct deduced_category + : mp11::mp_cond< + std::is_same, detail::boolean_category, + std::is_integral, detail::integer_category, + std::is_floating_point, detail::floating_point_category, + is_null_like, null_category, + is_string_like, string_category, + is_variant_like, variant_category, + is_optional_like, optional_category, + is_map_like, map_category, + is_sequence_like, sequence_category, + is_tuple_like, tuple_category, + is_described_class, described_class_category, + is_described_enum, described_enum_category, + is_path_like, path_category, + // failed to find a suitable implementation + mp11::mp_true, unknown_category> +{ }; -// generic conversions -template< class T > -using generic_conversion_category = mp11::mp_cond< - // std::is_same>, init_list_tag, - std::is_same, value_ref_tag, - - std::is_same, bool_conversion_tag, - std::is_integral, integral_conversion_tag, - std::is_floating_point, floating_point_conversion_tag, - is_null_like, null_like_conversion_tag, - is_string_like, string_like_conversion_tag, - is_variant_like, variant_conversion_tag, - is_optional_like, optional_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_path_like, path_conversion_tag, - // failed to find a suitable implementation - mp11::mp_true, no_conversion_tag>; +struct no_context {}; + +template +using use_category_helper = use_category< + T, mp11::mp_if, void, Ctx>>; + +template< class Dir > +struct all_custom_checks +{ + template + using fn = mp11::mp_list< + mp11::mp_defer, + mp11::mp_defer>; +}; + +template< class Dir > +struct all_fallback_checks +{ + template + using fn = mp11::mp_list< + mp11::mp_defer, + mp11::mp_defer, + mp11::mp_defer, + mp11::mp_defer>; +}; + +template +using direct_custom_checks = mp11::mp_list< + mp11::mp_defer>; + +template +using direct_fallback_checks = mp11::mp_list< + mp11::mp_defer, + mp11::mp_defer>; + +template +using no_checks = mp11::mp_list<>; 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, +using get_conversion_category_helper = mp11::mp_eval_if_c< + conversion_category::unknown != T1::value, T1, mp11::mp_eval_or_q, T1, mp11::mp_quote, T2>; -template< class Ctx, class T, class Dir > -struct conversion_category_impl + +template< + class T, + class Ctx, + template class CustomChecks, + template class FallbackChecks> +struct get_conversion_category_impl { - using type = mp11::mp_fold< - mp11::mp_list< - mp11::mp_defer, - mp11::mp_defer, - mp11::mp_defer>, - no_conversion_tag, - conversion_category_impl_helper>; + using type = std::integral_constant< + conversion_category, + mp11::mp_fold< + mp11::mp_append< CustomChecks, FallbackChecks >, + unknown_category, + get_conversion_category_helper>::value>; }; -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 Ctx, + template class CustomChecks, + template class FallbackChecks> +using get_conversion_category = typename get_conversion_category_impl< + T, Ctx, CustomChecks, FallbackChecks>::type; -template< class T, class Dir, class... Ctxs > -struct conversion_category_impl< std::tuple, T, Dir > +template< class T > +using any_conversion_tag = mp11::mp_not< std::is_same >; + +template< + class T, + template class CustomChecks, + template class FallbackChecks, + class... Ctxs > +struct get_conversion_category_impl< + T, std::tuple, CustomChecks, FallbackChecks> { 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 >; -}; + get_conversion_category< + T, remove_cvref, CustomChecks, no_checks>... >; -struct no_context -{}; + using custom_index = mp11::mp_find_if< cats, any_conversion_tag >; + using is_custom = mp11::mp_less< custom_index, mp11::mp_size >; + + using index = mp11::mp_if< is_custom, custom_index, mp11::mp_size_t<0> >; + + using fallback_cat = mp11::mp_fold< + FallbackChecks, unknown_category, get_conversion_category_helper>; + + using type = std::integral_constant< + conversion_category, + mp11::mp_eval_if_not< + is_custom, fallback_cat, mp11::mp_at, cats, index>::value>; +}; template using can_convert = mp11::mp_not< std::is_same< - detail::conversion_category, - detail::no_conversion_tag>>; - -template -using conversion_round_trips_helper = mp11::mp_or< - std::is_same, - 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>>; + get_conversion_category< + T, + no_context, + all_custom_checks::template fn, + all_fallback_checks::template fn>, + unknown_category>>; template< class T1, class T2 > struct copy_cref_helper @@ -501,7 +572,11 @@ template< class T, class Dir, class... Ctxs > struct supported_context< std::tuple, T, Dir > { using Ctx = std::tuple; - using impl = conversion_category_impl; + using impl = get_conversion_category_impl< + T, + Ctx, + all_custom_checks::template fn, + all_fallback_checks::template fn>; using index = typename impl::index; using next_supported = supported_context< mp11::mp_at< typename impl::ctxs, index >, T, Dir >; diff --git a/include/boost/json/impl/serializer.hpp b/include/boost/json/impl/serializer.hpp index 0a7255d06..b2edab351 100644 --- a/include/boost/json/impl/serializer.hpp +++ b/include/boost/json/impl/serializer.hpp @@ -54,7 +54,7 @@ write_impl(writer& w, stream& ss); template BOOST_FORCEINLINE bool -write_impl(null_like_conversion_tag, writer& w, stream& ss) +write_impl(null_category, writer& w, stream& ss) { #if defined(_MSC_VER) # pragma warning( push ) @@ -71,7 +71,7 @@ write_impl(null_like_conversion_tag, writer& w, stream& ss) template BOOST_FORCEINLINE bool -write_impl(bool_conversion_tag, writer& w, stream& ss) +write_impl(boolean_category, writer& w, stream& ss) { BOOST_ASSERT( w.p_ ); auto const t = *reinterpret_cast(w.p_); @@ -97,7 +97,7 @@ write_impl(bool_conversion_tag, writer& w, stream& ss) template BOOST_FORCEINLINE bool -write_impl(integral_conversion_tag, writer& w, stream& ss0) +write_impl(integer_category, writer& w, stream& ss0) { #if defined(_MSC_VER) # pragma warning( push ) @@ -161,7 +161,7 @@ write_impl(integral_conversion_tag, writer& w, stream& ss0) template BOOST_FORCEINLINE bool -write_impl(floating_point_conversion_tag, writer& w, stream& ss0) +write_impl(floating_point_category, writer& w, stream& ss0) { #if defined(_MSC_VER) # pragma warning( push ) @@ -182,7 +182,7 @@ write_impl(floating_point_conversion_tag, writer& w, stream& ss0) template BOOST_FORCEINLINE bool -write_impl(string_like_conversion_tag, writer& w, stream& ss0) +write_impl(string_category, writer& w, stream& ss0) { #if defined(_MSC_VER) # pragma warning( push ) @@ -204,7 +204,7 @@ write_impl(string_like_conversion_tag, writer& w, stream& ss0) template BOOST_FORCEINLINE bool -write_impl(sequence_conversion_tag, writer& w, stream& ss0) +write_impl(sequence_category, writer& w, stream& ss0) { using It = iterator_type; using Elem = value_type; @@ -276,7 +276,7 @@ write_impl(sequence_conversion_tag, writer& w, stream& ss0) template BOOST_FORCEINLINE bool -write_impl(map_like_conversion_tag, writer& w, stream& ss0) +write_impl(map_category, writer& w, stream& ss0) { using It = iterator_type; using Mapped = mapped_type; @@ -395,7 +395,7 @@ struct serialize_tuple_elem_helper template BOOST_FORCEINLINE bool -write_impl(tuple_conversion_tag, writer& w, stream& ss0) +write_impl(tuple_category, writer& w, stream& ss0) { T const* pt; local_stream ss(ss0); @@ -522,7 +522,7 @@ struct serialize_struct_elem_helper template BOOST_FORCEINLINE bool -write_impl(described_class_conversion_tag, writer& w, stream& ss0) +write_impl(described_class_category, writer& w, stream& ss0) { using Ds = described_members; @@ -598,7 +598,7 @@ write_impl(described_class_conversion_tag, writer& w, stream& ss0) template BOOST_FORCEINLINE bool -write_impl(described_enum_conversion_tag, writer& w, stream& ss) +write_impl(described_enum_category, writer& w, stream& ss) { #ifdef BOOST_DESCRIBE_CXX14 using Integer = typename std::underlying_type::type; @@ -682,7 +682,7 @@ struct serialize_variant_elem_helper template BOOST_FORCEINLINE bool -write_impl(variant_conversion_tag, writer& w, stream& ss) +write_impl(variant_category, writer& w, stream& ss) { T const* pt; @@ -726,7 +726,7 @@ write_impl(variant_conversion_tag, writer& w, stream& ss) template BOOST_FORCEINLINE bool -write_impl(optional_conversion_tag, writer& w, stream& ss) +write_impl(optional_category, writer& w, stream& ss) { using Elem = value_result_type; @@ -775,7 +775,7 @@ write_impl(optional_conversion_tag, writer& w, stream& ss) template BOOST_FORCEINLINE bool -write_impl(path_conversion_tag, writer& w, stream& ss) +write_impl(path_category, writer& w, stream& ss) { #if defined(_MSC_VER) # pragma warning( push ) @@ -823,7 +823,8 @@ template bool write_impl(writer& w, stream& ss) { - using cat = detail::generic_conversion_category; + using cat = get_conversion_category< + T, void, direct_custom_checks, direct_fallback_checks>; return write_impl( cat(), w, ss ); } diff --git a/include/boost/json/impl/serializer.ipp b/include/boost/json/impl/serializer.ipp index 15e46c909..09f601e2c 100644 --- a/include/boost/json/impl/serializer.ipp +++ b/include/boost/json/impl/serializer.ipp @@ -362,7 +362,7 @@ write_value(writer& w, stream& ss); template< class T, bool StackEmpty > BOOST_FORCEINLINE bool -write_impl(no_conversion_tag, writer& w, stream& ss) +write_impl(unknown_category, writer& w, stream& ss) { return write_value(w, ss); } @@ -371,14 +371,14 @@ template bool write_array(writer& w, stream& ss) { - return write_impl(sequence_conversion_tag(), w, ss); + return write_impl(sequence_category(), w, ss); } template bool write_object(writer& w, stream& ss) { - return write_impl(map_like_conversion_tag(), w, ss); + return write_impl(map_category(), w, ss); } template diff --git a/include/boost/json/value_from.hpp b/include/boost/json/value_from.hpp index 984c32a74..f25ac0c2a 100644 --- a/include/boost/json/value_from.hpp +++ b/include/boost/json/value_from.hpp @@ -20,43 +20,61 @@ 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 + This function attempts to convert an object of type `T` to @ref value using - @li one of @ref value's constructors, + - one of @ref value's constructors, + - a library-provided generic conversion, or + - a user-provided overload of `tag_invoke`. - @li a library-provided generic conversion, or + In order to perform the conversion the function selects an appropriate + implementation based on the types `T` and `Context` (if provided). - @li a user-provided overload of `tag_invoke`. + 1. If `Context` is available and is not `std::tuple` - Out of the function supports default constructible types satisfying - {req_SequenceContainer}, arrays, arithmetic types, `bool`, `std::tuple`, - `std::pair`, `std::optional`, `std::variant`, `std::nullptr_t`, and structs - and enums described using Boost.Describe. + a. check if `use_category::value` is not + @ref conversion_category::unknown; otherwise - Conversion of other types is done by calling an overload of `tag_invoke` - found by argument-dependent lookup. Its signature should be similar to: + b. check if a `tag_invoke` overload from the list below that takes a + `Context const&` exists. + + 2. Otherwise, if `Context` is available, and is `std::tuple` repeat + steps **1** and **2** recursively for every `C` until either + step **1.a** or **1.b** succeeds for some `C`. + + 3. Failing that, + + a. check if `use_category::value` is not + @ref conversion_category::unknown; otherwise + + b. check if a `tag_invoke` overload from the list below that takes only + 2 parameters exists; otherwise + + c. check if `T` is one of @ref value, @ref array, @ref object, + or @ref string; otherwise + + d. check if `T` matches one of the categories of types described in the + table "Conversion categories" in \<\> section. + + These steps determine both the appropriate category of conversion for `T`, + and, if necessary, the effective context `C` that will be used for + conversion. If the category is selected on steps **1.a**, **3.a**, **3.c**, + or **3.d**, the library provides a suitable conversion implementation. + If the category is selected on steps **2.b** or **3.b**, then a + user-provided `tag_invoke` overload is used. + + The overloads of `tag_invoke` that will be considered by this function + are in the following list. Overloads that appear higher in the list have + higher priority. @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. @@ -90,9 +108,6 @@ value_from( value& jv) { using bare_T = detail::remove_cvref; - BOOST_CORE_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 ); } diff --git a/include/boost/json/value_to.hpp b/include/boost/json/value_to.hpp index 9fe215861..eb43b223b 100644 --- a/include/boost/json/value_to.hpp +++ b/include/boost/json/value_to.hpp @@ -20,43 +20,74 @@ namespace json { /** Convert a @ref value to an object of type `T`. - This function attempts to convert a @ref value - to `T` using + 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`. + - one of @ref value's accessors, or + - a library-provided generic conversion, or + - a user-provided overload of `tag_invoke`. - Out of the box the function supports default constructible types satisfying - {req_SequenceContainer}, arrays, arithmetic types, `bool`, `std::tuple`, - `std::pair`, `std::optional`, `std::variant`, `std::nullptr_t`, and structs - and enums described using Boost.Describe. + In order to perform the conversion the function selects an appropriate + implementation based on the types `T` and `Context` (if provided). - Conversion of other types is done by calling an overload of `tag_invoke` - found by argument-dependent lookup. Its signature should be similar to: + 1. If `Context` is available and is not `std::tuple` + + a. check if `use_category::value` is not + @ref conversion_category::unknown; otherwise + + b. check if a `tag_invoke` overload from the list below that takes a + `Context const&` exists. + + 2. Otherwise, if `Context` is available, and is `std::tuple` repeat + steps **1** and **2** recursively for every `C` until either + step **1.a** or **1.b** succeeds for some `C`. + + 3. Failing that, + + a. check if `use_category::value` is not + @ref conversion_category::unknown; otherwise + + b. check if a `tag_invoke` overload from the list below that takes only + 2 parameters exists; otherwise + + c. check if `T` is one of @ref value, @ref array, @ref object, + or @ref string; otherwise + + d. check if `T` matches one of the categories of types described in the + table "Conversion categories" in \<\> section. + + These steps determine both the appropriate category of conversion for `T`, + and, if necessary, the effective context `C` that will be used for + conversion. If the category is selected on steps **1.a**, **3.a**, **3.c**, + or **3.d**, the library provides a suitable conversion implementation. + If the category is selected on steps **2.b** or **3.b**, then a + user-provided `tag_invoke` overload is used. + + The overloads of `tag_invoke` that will be considered by this function + are in the following list. Overloads that appear higher in the list have + higher priority. @code template< class FullContext > - T tag_invoke( value_to_tag, const value&, const Context& , const FullContext& ); - @endcode + result tag_invoke( try_value_to_tag, const value&, const Context&, const FullContext& ); - or + template< class FullContext > + T tag_invoke( value_to_tag, const value&, const Context&, const FullContext& ); + + result tag_invoke( try_value_to_tag, const value&, const Context& ); - @code T tag_invoke( value_to_tag, const value&, const Context& ); - @endcode - or + result tag_invoke( try_value_to_tag, const value& ); - @code - result tag_invoke( value_to_tag, const value& ); + T 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. + For `tag_invoke` overloads that take a parameter of type `value_to_tag` + the object returned by a call to that overload is returned by the function + as the result of the conversion. For overloads that take a parameter of + type `try_value_to_tag` if the returned `result` contains a value, that + value is returned as the result of the conversion. Otherwise, an exception + of type @ref boost::system::system_error that stores the error is thrown. 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. @@ -81,13 +112,13 @@ namespace json { @tparam Context The type of context passed to the conversion function. - @returns `jv` converted to `result`. + @returns `jv` converted to `T`. @param jv The @ref value to convert. @param ctx Context passed to the conversion function. - @see @ref value_to_tag, @ref value_from, + @see @ref try_value_to, @ref value_from, tag_invoke: A general pattern for supporting customisable functions @@ -99,9 +130,6 @@ value_to( value const& jv, Context const& ctx ) { BOOST_CORE_STATIC_ASSERT( ! std::is_reference::value ); using bare_T = detail::remove_cvref; - BOOST_CORE_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 ); } @@ -128,42 +156,76 @@ value_to(U const& jv) = delete; 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`. + - one of @ref value's accessors, or + - a library-provided generic conversion, or + - a user-provided overload of `tag_invoke`. + + In order to perform the conversion the function selects an appropriate + implementation based on the types `T` and `Context` (if provided). + + 1. If `Context` is available and is not `std::tuple` + + a. check if `use_category::value` is not + @ref conversion_category::unknown; otherwise + + b. check if a `tag_invoke` overload from the list below that takes a + `Context const&` exists. + + 2. Otherwise, if `Context` is available, and is `std::tuple` repeat + steps **1** and **2** recursively for every `C` until either + step **1.a** or **1.b** succeeds for some `C`. + + 3. Failing that, + + a. check if `use_category::value` is not + @ref conversion_category::unknown; otherwise + + b. check if a `tag_invoke` overload from the list below that takes only + 2 parameters exists; otherwise - Out of the box the function supports default constructible types satisfying - {req_SequenceContainer}, arrays, arithmetic types, `bool`, `std::tuple`, - `std::pair`, `std::optional`, `std::variant`, `std::nullptr_t`, and structs - and enums described using Boost.Describe. + c. check if `T` is one of @ref value, @ref array, @ref object, + or @ref string; otherwise - Conversion of other types is done by calling an overload of `tag_invoke` - found by argument-dependent lookup. Its signature should be similar to: + d. check if `T` matches one of the categories of types described in the + table "Conversion categories" in \<\> section. + + These steps determine both the appropriate category of conversion for `T`, + and, if necessary, the effective context `C` that will be used for + conversion. If the category is selected on steps **1.a**, **3.a**, **3.c**, + or **3.d**, the library provides a suitable conversion implementation. + If the category is selected on steps **2.b** or **3.b**, then a + user-provided `tag_invoke` overload is used. + + The overloads of `tag_invoke` that will be considered by this function + are in the following list. Overloads that appear higher in the list have + higher priority. @code template< class FullContext > - result tag_invoke( try_value_to_tag, const value&, const Context& , const FullContext& ); - @endcode + result tag_invoke( try_value_to_tag, const value&, const Context&, const FullContext& ); - or + template< class FullContext > + T tag_invoke( value_to_tag, const value&, const Context&, const FullContext& ); - @code result tag_invoke( try_value_to_tag, const value&, const Context& ); - @endcode - or + T tag_invoke( value_to_tag, const value&, const Context& ); - @code result tag_invoke( try_value_to_tag, const value& ); + + T 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. + For `tag_invoke` overloads that take a parameter of type + `try_value_to_tag` the object returned by a call to that overload is + returned by the function as the result of the conversion. For overloads + that take a parameter of type `value_to_tag` the returned object is + wrapped with a `result`. 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 + will return @ref error::exception, unless the exception type is @ref std::bad_alloc, which will be allowed to propagate. The `ctx` argument can be used either as a tag type to provide conversions @@ -196,9 +258,6 @@ try_value_to( value const& jv, Context const& ctx ) { BOOST_CORE_STATIC_ASSERT( ! std::is_reference::value ); using bare_T = detail::remove_cvref; - BOOST_CORE_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 ); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 188a7be1f..2222c82b2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -32,7 +32,9 @@ list(FILTER BOOST_JSON_TESTS_FILES EXCLUDE REGEX printers\.cpp$) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${BOOST_JSON_TESTS_FILES}) add_executable(boost_json-tests ${EXCLUDE_TESTS_FROM_ALL} ${BOOST_JSON_TESTS_FILES}) target_include_directories(boost_json-tests PRIVATE .) -target_link_libraries(boost_json-tests PRIVATE Boost::json) +target_link_libraries(boost_json-tests PRIVATE Boost::json + Boost::optional +) target_compile_options(boost_json-tests PRIVATE $<$:/bigobj> ) diff --git a/test/conversion.cpp b/test/conversion.cpp index b566bf58d..ede9ae3b5 100644 --- a/test/conversion.cpp +++ b/test/conversion.cpp @@ -16,7 +16,25 @@ #include #include -#include "test.hpp" +#include + +#include +#include +#include +#include + +#ifndef BOOST_NO_CXX17_HDR_FILESYSTEM +# include +#endif // BOOST_NO_CXX17_HDR_FILESYSTEM + +#ifndef BOOST_NO_CXX17_HDR_OPTIONAL +# include +#endif // BOOST_NO_CXX17_HDR_OPTIONAL + +#ifndef BOOST_NO_CXX17_HDR_VARIANT +# include +#endif // BOOST_NO_CXX17_HDR_VARIANT + #include "test_suite.hpp" #ifdef __clang__ @@ -24,8 +42,116 @@ # pragma clang diagnostic ignored "-Wunused-private-field" #endif -namespace -{ +struct ctx0 {}; +struct ctx1 {}; + +struct my_null1 { }; +struct my_null2 { }; +struct my_null3 { }; + +struct custom1 { }; +struct custom2 { }; +struct custom3 { }; +struct custom4 { }; +struct custom5 { }; +struct custom6 { }; +struct custom7 { }; +struct custom8 { }; +struct custom9 { }; +struct custom10 { }; +struct custom11 { }; +struct custom12 { }; +struct custom13 { }; + +void +tag_invoke(boost::json::value_from_tag, boost::json::value&, custom1 const&); +void +tag_invoke(boost::json::value_from_tag, boost::json::value&, custom10 const&); + +void +tag_invoke( + boost::json::value_from_tag, + boost::json::value&, + custom4 const&, + ctx1 const&); +void +tag_invoke( + boost::json::value_from_tag, + boost::json::value&, + custom11 const&, + ctx1 const&); +void +tag_invoke( + boost::json::value_from_tag, + boost::json::value&, + custom12 const&, + ctx1 const&); +void +tag_invoke( + boost::json::value_from_tag, + boost::json::value&, + custom13 const&, + ctx0 const&); + +template +void +tag_invoke( + boost::json::value_from_tag, + boost::json::value&, + custom7 const&, + ctx1 const&, + Ctx const&); + +custom2 +tag_invoke(boost::json::value_to_tag, boost::json::value const&); +custom10 +tag_invoke(boost::json::value_to_tag, boost::json::value const&); + +custom5 +tag_invoke( + boost::json::value_to_tag, + boost::json::value const&, + ctx1 const&); +custom11 +tag_invoke( + boost::json::value_to_tag, + boost::json::value const&, + ctx1 const&); +custom12 +tag_invoke( + boost::json::value_to_tag, + boost::json::value const&, + ctx1 const&); +custom13 +tag_invoke( + boost::json::value_to_tag, + boost::json::value const&, + ctx0 const&); + +template +custom8 +tag_invoke( + boost::json::value_to_tag, + boost::json::value const&, + ctx1 const&, + Ctx const&); + +boost::system::result +tag_invoke(boost::json::try_value_to_tag, boost::json::value const&); + +boost::system::result +tag_invoke( + boost::json::try_value_to_tag, + boost::json::value const&, + ctx1 const&); + +template +boost::system::result +tag_invoke( + boost::json::try_value_to_tag, + boost::json::value const&, + ctx1 const&, + Ctx const&); struct pseudo_string1 { @@ -44,6 +170,22 @@ struct pseudo_sequence1 struct pseudo_sequence2 : pseudo_sequence1 { }; +struct pseudo_sequence3 : pseudo_sequence1 +{ }; + +struct pseudo_sequence4 : pseudo_sequence1 +{ }; + +struct pseudo_sequence5 : pseudo_sequence1 +{ }; + +void +tag_invoke( + boost::json::value_from_tag, boost::json::value&, pseudo_sequence5 const&); +pseudo_sequence5 +tag_invoke( + boost::json::value_to_tag, boost::json::value const&); + struct pseudo_tuple1 { }; @@ -72,8 +214,6 @@ struct pseudo_multimap1 }; -struct my_null { }; - struct described1 { int n1; }; BOOST_DESCRIBE_STRUCT(described1, (), (n1)) @@ -136,8 +276,6 @@ BOOST_DESCRIBE_ENUM(described_enum, e) enum class undescribed_enum { }; -} // namespace - namespace std { @@ -158,6 +296,17 @@ struct tuple_size : std::integral_constant namespace boost { namespace json { +template <> struct use_category : null_category { }; +template <> struct use_category : null_category { }; +template <> struct use_category : null_category { }; +template <> struct use_category : null_category { }; +template <> struct use_category : null_category { }; +template <> struct use_category : null_category { }; + +template <> +struct is_null_like : std::true_type +{ }; + template <> struct is_string_like : std::false_type { }; @@ -166,57 +315,923 @@ template <> struct is_sequence_like : std::false_type { }; +template <> struct use_category : map_category { }; +template <> struct use_category : map_category { }; + template <> struct is_tuple_like : std::false_type { }; -template <> -struct is_null_like : std::true_type -{ }; +template< class T, class Ctx = void > +using get_category1 = detail::get_conversion_category< + T, + Ctx, + detail::all_custom_checks::fn, + detail::all_fallback_checks::fn>; + +template< class T, class Ctx = void > +using get_category2 = detail::get_conversion_category< + T, + Ctx, + detail::all_custom_checks::fn, + detail::all_fallback_checks::fn>; + +template< class T, class Ctx = void > +using get_category3 = detail::get_conversion_category< + T, Ctx, detail::direct_custom_checks, detail::direct_fallback_checks>; class conversion_test { + template + static constexpr + bool + is_category() + { + return std::is_same::value; + } + + template + static constexpr + bool + check_selected_context() + { + using Sup = detail::supported_context; + using Get = decltype(Sup::get( std::declval() )); + using R = detail::remove_cvref; + return std::is_same::value; + } + + template< class T, class Cat > + void test_deduced() + { + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + } + + template< class T, class Cat > + void test_native() + { + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + } + + void test_variant2_deduced() + { + using Cat = variant_category; + using variant = variant2::variant; + + // These two fail, because Variant2 has tag_invoke overloads + // BOOST_CORE_STATIC_ASSERT( + // is_category, Cat>()); + // BOOST_CORE_STATIC_ASSERT( + // is_category, Cat>()); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category3>, Cat>() )); + } + + void + test_customised() + { + test_deduced(); + + using Cat = null_category; + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category3>, Cat>() )); + } + + void + test_tag_invoke() + { + using Cat = detail::user_category; + + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category2>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category3>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom1, + std::tuple, + detail::value_from_conversion>() )); + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom1, + std::tuple, + detail::value_to_conversion>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category1>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category3>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom2, + std::tuple, + detail::value_from_conversion>() )); + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom2, + std::tuple, + detail::value_to_conversion>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category1>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category3>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom3, + std::tuple, + detail::value_from_conversion>() )); + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom3, + std::tuple, + detail::value_to_conversion>() )); + } + + void + test_tag_invoke_context() + { + using Cat = detail::user_context_category; + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category2>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category3>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx1, + custom4, + std::tuple, + detail::value_from_conversion>() )); + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom4, + std::tuple, + detail::value_to_conversion>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category1>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category3>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom5, + std::tuple, + detail::value_from_conversion>() )); + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx1, + custom5, + std::tuple, + detail::value_to_conversion>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category1>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category3>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom6, + std::tuple, + detail::value_from_conversion>() )); + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx1, + custom6, + std::tuple, + detail::value_to_conversion>() )); + } + + void + test_tag_invoke_full_context() + { + using Cat = detail::user_full_context_category; + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category2>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category3>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx1, + custom7, + std::tuple, + detail::value_from_conversion>() )); + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom7, + std::tuple, + detail::value_to_conversion>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category1>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category3>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom8, + std::tuple, + detail::value_from_conversion>() )); + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx1, + custom8, + std::tuple, + detail::value_to_conversion>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category1>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2>, Cat>() )); + BOOST_CORE_STATIC_ASSERT(( + !is_category< + get_category3>, Cat>() )); + + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx0, + custom9, + std::tuple, + detail::value_from_conversion>() )); + BOOST_CORE_STATIC_ASSERT(( + check_selected_context< + ctx1, + custom9, + std::tuple, + detail::value_to_conversion>() )); + } + + void + test_complex_customisations() + { + // the type is deduced as a sequence, but use_category makes it a map + BOOST_CORE_STATIC_ASSERT(( + is_category, map_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, map_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, map_category>() )); + + // the type is deduced as a sequence, but use_category makes it a map + // in context ctx1 + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1, sequence_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2, sequence_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category3, sequence_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1, map_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2, map_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category3, map_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1>, + map_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2>, + map_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category3>, + map_category>() )); + + // the type is deduced as a sequence, but there's a tag_invoke overload + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1, detail::user_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2, detail::user_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category3, sequence_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1, + detail::user_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2, + detail::user_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category3, + sequence_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1>, + detail::user_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2>, + detail::user_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category3>, + sequence_category>() )); + + // the type has a tag_invoke overload, but also has use_category + BOOST_CORE_STATIC_ASSERT(( + is_category, null_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, null_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, null_category>() )); + + // the type has a tag_invoke overload in context ctx1, and + // also has use_category (context-less) + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1, + detail::user_context_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2, + detail::user_context_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category3, null_category>() )); + + // the type has both a tag_invoke overload and use_category + // in context ctx1 + BOOST_CORE_STATIC_ASSERT(( + is_category, null_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, null_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category, null_category>() )); + + // the type has a tag_invoke overload in ctx0 and use_category + // in context ctx1 + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category1>, + detail::user_context_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category2>, + detail::user_context_category>() )); + BOOST_CORE_STATIC_ASSERT(( + is_category< + get_category3>, + null_category>() )); + } + public: void run() { - BOOST_CORE_STATIC_ASSERT( is_string_like::value ); + test_deduced(); + test_deduced(); + test_deduced(); + test_deduced(); + test_deduced(); + test_deduced(); + test_deduced(); + test_deduced(); + test_deduced(); + test_deduced(); + test_variant2_deduced(); + test_deduced, optional_category>(); + test_deduced, map_category>(); + test_deduced, map_category>(); + test_deduced, map_category>(); + test_deduced, sequence_category>(); + test_deduced, sequence_category>(); + test_deduced(); + test_deduced, sequence_category>(); + test_deduced, tuple_category>(); + test_deduced, tuple_category>(); + test_deduced, tuple_category>(); + test_deduced(); +#ifndef BOOST_NO_CXX17_HDR_VARIANT + test_deduced(); + test_deduced, variant_category>(); +#endif // BOOST_NO_CXX17_HDR_VARIANT +#ifndef BOOST_NO_CXX17_HDR_OPTIONAL + test_deduced, optional_category>(); +#endif // BOOST_NO_CXX17_HDR_OPTIONAL +#ifdef BOOST_DESCRIBE_CXX14 + test_deduced(); + test_deduced(); + test_deduced(); + test_deduced(); + test_deduced(); +#endif // BOOST_DESCRIBE_CXX14 +#ifndef BOOST_NO_CXX17_HDR_FILESYSTEM + test_deduced(); +#endif // BOOST_NO_CXX17_HDR_FILESYSTEM + + test_native(); + test_native(); + test_native(); + test_native(); + test_native(); + + test_customised(); + test_tag_invoke(); + test_tag_invoke_context(); + test_tag_invoke_full_context(); + test_complex_customisations(); + BOOST_CORE_STATIC_ASSERT( !is_string_like::value ); BOOST_CORE_STATIC_ASSERT( is_sequence_like::value ); - BOOST_CORE_STATIC_ASSERT( !is_sequence_like::value ); - BOOST_CORE_STATIC_ASSERT( is_tuple_like::value ); BOOST_CORE_STATIC_ASSERT( !is_tuple_like::value ); - BOOST_CORE_STATIC_ASSERT( - is_map_like< pseudo_map1 >::value ); BOOST_CORE_STATIC_ASSERT( !is_map_like< pseudo_map1 >::value ); BOOST_CORE_STATIC_ASSERT( !is_map_like< pseudo_multimap1 >::value ); - BOOST_CORE_STATIC_ASSERT( is_null_like::value ); - BOOST_CORE_STATIC_ASSERT( is_null_like::value ); - #ifdef BOOST_DESCRIBE_CXX14 - BOOST_CORE_STATIC_ASSERT( is_described_class::value ); - BOOST_CORE_STATIC_ASSERT( is_described_class::value ); - BOOST_CORE_STATIC_ASSERT( is_described_class::value ); - BOOST_CORE_STATIC_ASSERT( is_described_class::value ); - - BOOST_CORE_STATIC_ASSERT( !is_described_class::value ); BOOST_CORE_STATIC_ASSERT( !is_described_class::value ); BOOST_CORE_STATIC_ASSERT( !is_described_class::value ); BOOST_CORE_STATIC_ASSERT( !is_described_class::value ); BOOST_CORE_STATIC_ASSERT( !is_described_class::value ); BOOST_CORE_STATIC_ASSERT( !is_described_class::value ); - BOOST_CORE_STATIC_ASSERT( is_described_enum::value ); - BOOST_CORE_STATIC_ASSERT( !is_described_enum::value ); + BOOST_CORE_STATIC_ASSERT( !is_described_enum::value ); BOOST_CORE_STATIC_ASSERT( !is_described_enum::value ); - BOOST_CORE_STATIC_ASSERT( - !is_described_enum::value); + BOOST_CORE_STATIC_ASSERT( !is_described_enum::value); #endif BOOST_CORE_STATIC_ASSERT(( diff --git a/test/snippets.cpp b/test/snippets.cpp index 93dd8ef5c..9a58fcc57 100644 --- a/test/snippets.cpp +++ b/test/snippets.cpp @@ -32,14 +32,11 @@ #include "doc_types.hpp" // tag::snippet_conv_spec_trait2[] -namespace boost -{ -namespace json -{ +namespace boost { +namespace json { template<> -struct is_sequence_like< user_ns::ip_address > - : std::false_type +struct use_category : tuple_category { }; } // namespace json @@ -78,7 +75,7 @@ tag_invoke( const value_to_tag< ip_address >&, value const& jv ) // end::snippet_tag_invoke_1[] // tag::snippet_nothrow_1[] -result_for< ip_address, value >::type +boost::system::result tag_invoke( const try_value_to_tag< ip_address >&, value const& jv ) { if( !jv.is_array() ) diff --git a/test/value_to.cpp b/test/value_to.cpp index 2906d8649..7c5ac0fa6 100644 --- a/test/value_to.cpp +++ b/test/value_to.cpp @@ -603,16 +603,6 @@ class value_to_test { // using result { - // 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_CORE_STATIC_ASSERT(( - detail::conversion_round_trips< - mp11::mp_first< mp11::mp_list >, - ::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() );