boost/libs/json/doc/qbk/03_06_conversion.qbk
2021-10-05 21:37:46 +02:00

357 lines
10 KiB
Plaintext

[/
Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
Copyright (c) 2020 Krystian Stasiowski (sdkrystian@gmail.com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Official repository: https://github.com/cppalliance/json
]
[/-----------------------------------------------------------------------------]
[section:conversion Value Conversion]
While the __value__ container makes it easy to create ad-hoc structures,
often it is necessary to convert between JSON and specific user-defined
types. Converting from a type `T` to __value__ is done by __value_from__.
The conversion in the opposite direction is done with __value_to__.
[snippet_conv_1]
[heading Customization Points]
A ['customization point] is a mechanism where a library delegates behavior
of some operation to the user, or gives the user the option of controlling
the behavior of some operation for a specific type.
Within the standard library, the `swap` function is a customization point
that uses [@https://en.cppreference.com/w/cpp/language/adl argument-dependent lookup]
to find user-provided overloads within the namespace of the arguments:
[snippet_conv_2]
Another example would be the class template `std::hash`, which can be
specialized for some type `T` to implement custom behavior:
[snippet_conv_3]
While these mechanisms work, they are not without problems.
Boost.JSON implements value conversion customization points using
the `tag_invoke` mechanism outlined in
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf P1895],
allowing users to define conversions to and from their own types.
In essence, `tag_invoke` provides a uniform interface
for defining customization points by using argument-dependent lookup
to find a viable function from the point at which it's called.
As the name suggests, a tag type is passed as an argument in order to:
* discard candidates that are unrelated to that particular customization point, and
* embed the user-defined type into the arguments list (e.g. by using a tag type template
such as `value_to_tag<T>`) so that its
[@http://eel.is/c++draft/basic.lookup.argdep#2 associated namespaces and entities] are examined
when name lookup is performed.
This has the effect of finding user-provided `tag_invoke` overloads, even if they
are declared (lexically) after the definition of the calling function.
[heading `tag_invoke` overloads]
In all cases, conversions are done by calling an appropriate
overload of `tag_invoke`. For __value_from__, these have the form:
```
void tag_invoke( const value_from_tag&, value&, T );
```
Likewise, the overloads of `tag_invoke` called by __value_to__
take the form:
```
T tag_invoke( const value_to_tag< T >&, const value& );
```
In both cases, overloads for user-provided types can be implemented:
[snippet_conv_4]
Since the type being converted is embedded into the
function's signature, user-provided overloads
are visible to argument-dependent lookup and will be candidates
when a conversion is performed:
[snippet_conv_5]
When __value_from__ is called, the `tag_invoke` function template
will be found by argument-dependent lookup and used to perform the conversion:
[snippet_conv_6]
In addition to user-provided overloads of `tag_invoke`, the library
will add its own function to the overload set when certain constraints
are satisfied. The library provided overloads have no special
prioritization over those provided by the user, so care should be taken to
avoid writing ambiguous declarations:
[snippet_conv_7]
Upon calling this function, overload resolution will fail because
the library already provides an overload for floating-point types:
[snippet_conv_8]
Library-provided overloads of `tag_invoke` come in two variants: those that
convert between JSON types (known as ['built-in conversions]),
and those that convert to/from container and string types
(known as ['generic conversions]). Generic conversions offer convenience
by eliminating the need to write repetitive overloads for types
that model common C++ concepts (e.g. sequence containers, associative containers,
tuples, and strings).
[snippet_conv_9]
[heading Converting to json::value]
The function template __value_from__ provides an interface to
construct a __value__ from a user- or library-provided
type `T`. The optionally supplied __storage_ptr__ argument is
used as the __memory_resource__ for the resulting __value__ object.
The parameter of type `value&` is the result
of the conversion; this ensures that the __storage_ptr__
is correctly propagated to the result. For example,
consider the following struct:
[snippet_conv_10]
If our store has a lot of customers,
it may be desirable to use a __monotonic_resource__ when serializing
`customer` objects to JSON. __value_from__ ensures that the
correct __memory_resource__ is used:
[snippet_conv_11]
In addition to the user-provided overloads found by
argument-dependent lookup, the library provides its own
overload of `tag_invoke` when certain conditions are met.
If, for the type `T` being converted
* `std::is_assignable<value&, T&&>::value` is `true`, or
* `T` satisfies ['StringLike], or
* `T` satisfies ['TupleLike], or
* `T` satisfies ['FromMapLike], or
* `T` satisfies ['FromContainerLike].
Then a function template of the form
```
template< class T >
void tag_invoke( value_from_tag, value& jv, T&& t );
```
is added to the set of user-provided overloads found by
argument-dependent lookup; it performs the conversion
corresponding to first condition met by `T` in the above
list. For example, if `T` satisfies both ['FromMapLike]
and ['FromContainerLike], the conversion will be performed
the one corresponding to ['FromMapLike]; it will not be ambiguous.
[snippet_conv_12]
The conversion performed when the first condition is met
(the library-provided built-in conversion) is assignment to the
__value__ parameter. For the generic conversions, types that satisfy
['TupleLike] or ['FromContainerLike] are converted to __array__,
those that satisfy ['FromMapLike] are converted to __object__,
and types that satisfy ['StringLike] are converted to __string__.
[heading Converting to Foreign Types]
The function template __value_to__ provides an interface to
construct a type `T` from a __value__. In contrast to
__value_from__, no output parameter is used as there is no
__storage_ptr__ to propagate.
[snippet_conv_13]
As with __value_from__, the library provides its own
overload of `tag_invoke` when certain conditions are met.
If, for the type `T`
* `T` is __value__, __object__, __array__, __string__, __string_view__,
__value_ref__, `std::initializer_list<value_ref>` or `bool`, or
if `std::is_arithmetic<T>::value` is `true`, or
* `T` satisfies ['StringLike], or
* `T` satisfies ['ToMapLike], or
* `T` satisfies ['ToContainerLike].
Then a function template of the form
```
template< class T >
T tag_invoke( value_to_tag< T >, const value& jv );
```
is added to the set of user-provided overloads found by
argument-dependent lookup. As with __value_from__, it performs
the conversion corresponding to first condition met
by `T` in the above list. Given the following definition of
`customer::customer( const value& )`:
[snippet_conv_14]
Objects of type `customer` can be converted to and from
__value__:
[snippet_conv_15]
When the first condition is met, the conversion will simply
return the object of type `T` stored within the __value__
(e.g. using `jv.as_object()`, `jv.as_array()`, etc).
When the second condition is met, the result of the conversion
will be `T(jv)`. As with __value_from__, when generic conversions
are selected, an attempt will be made to convert
the __value__ to `T`.
[snippet_conv_16]
[heading Named Requirements for Generic Conversions]
Each of the following tables specify valid operations on a
type or expression thereof meeting the requirement ['R].
A requirement ['Req] prefixed with ['From/To] does not
define a single requirement; it defines the two requirements
['FromReq] and ['ToReq] which correspond to
__value_to__ and __value_from__, respectively.
In each of the following:
* `T` is a type that satisfies ['R],
* `e` is an lvalue of type `T`,
* `has_value_trait` names the template __has_value_to__
(when ['R] is prefixed with ['To]) or __has_value_from__
(when ['R] is prefixed with ['From]).
* The namespace-scope declarations `using std::begin;` and
`using std::end;` precede the point at which the validity
and semantics of an expression is determined.
[h2 ['TupleLike] Requirements]
[table Valid expressions
[[Expression] [Type] [Semantics and Constraints]]
[
[`std::tuple_size<T>`]
[`U`]
[
['Constraints:] `U::value` is greater than `0`
]
]
]
[h2 ['StringLike] Requirements]
[table Valid expressions
[[Expression] [Type] [Semantics and Constraints]]
[
[`std::is_constructible<T, const char*, std::size_t>`]
[`U`]
[
['Constraints:] `U::value` is `true`
]
][
[`e.data()`]
[`pointer`]
[
['Constraints:] `std::is_convertible<pointer, const char*>::value` is `true`
]
][
[`e.size()`]
[`size_type`]
[
['Constraints:] `std::is_convertible<size_type, std::size_t>::value` is `true`
]
]
]
[h2 ['From/To ContainerLike] Requirements]
[table Valid expressions
[[Expression] [Type] [Semantics and Constraints]]
[
[`T::value_type`]
[`value_type`]
[
['Constraints:] `has_value_trait<value_type>::value` is `true`
]
][
[`begin(e)`]
[`iterator`]
[]
][
[`end(e)`]
[`iterator`]
[]
]
]
[h2 ['From/To MapLike] Requirements]
In the following table `vt` is a prvalue
of type `T::value_type`.
[table Valid expressions
[[Expression] [Type] [Semantics and Constraints]]
[
[`begin(e)`]
[`iterator`]
[]
][
[`end(e)`]
[`iterator`]
[]
][
[`T::value_type`]
[`pair_type`]
[
['Constraints:] `pair_type` satisfies ['TupleLike] and
`std::tuple_size<pair_type>::value` is `2`
]
][
[`std::tuple_element<0, pair_type>::type`]
[`key_type`]
[
['Constraints for FromMapLike:]
`std::is_convertible<key_type, string_view>::value` is `true`
['Constraints for ToMapLike:]
`std::is_constructible<key_type, string_view>::value` is `true`
]
][
[`std::tuple_element<1, pair_type>::type`]
[`value_type`]
[
['Constraints:] `has_value_trait<value_type>::value` is `true`
]
]
[
[`e.emplace(vt)`]
[`U`]
[
['Constraints:] `U` satisfies ['TupleLike]
]
]
]
[endsect]