diff --git a/3380_extend_cnttp_2/extend-cnttp-2.md b/3380_extend_cnttp_2/extend-cnttp-2.md index 30e8cfd1..447d7100 100644 --- a/3380_extend_cnttp_2/extend-cnttp-2.md +++ b/3380_extend_cnttp_2/extend-cnttp-2.md @@ -377,25 +377,35 @@ The original design used an `operator template` function with a constructor. The One of the issues with the serialization problem that we had to deal with was: how exactly do you serialize? What representation do you return? And then how do you deserialize again? This was where we got stuck with types like `std::vector` (which needs variable-length representation) and even `std::tuple` (which has a simple answer for serialization but you don't want to just create a whole new tuple type for this). Faisal Vali had the insight that reflection provides a very easy answer for this question: you serialize into (and then deserialize from) a range of `std::meta::info`! -At least, that's what [@P3380R0] did. A refinement of this is, rather than having the serialization function return a range of `std::meta::info`, we can accept a `Serializer` that can push a `meta::info` a `Deserializer` which can pop one. The proposed API for these types (whose actual type is `$unspecified$`): +At least, that's what [@P3380R0] did. A refinement of this is, rather than having the serialization function return a range of `std::meta::info`, we can accept a `Serializer` that can push a `meta::info` a `Deserializer` which can pop one. The proposed API for these types is:
Serialize | Deserialize |
---|---|
```cpp
-struct $Serializer$ {
- consteval auto push(std::meta::info r) -> void;
- consteval auto size() const -> size_t;
+namespace std::meta {
+
+class serializer {
+ vector |
```cpp
-struct $Deserializer$ {
- consteval auto pop() -> std::meta::info;
- consteval auto size() const -> size_t;
+namespace std::meta {
+
+class deserializer {
+ vector |
Deserializer
which can pop one. The
-proposed API for these types (whose actual type is
-unspecified
):
+proposed API for these types is:
@@ -1095,66 +1094,97 @@
struct Serializer {
-consteval auto push(std::meta::info r) -> void;
- consteval auto size() const -> size_t;
-
-template <class T>
- consteval auto push_value(T const& value) -> void {
- (std::meta::reflect_value(value));
- push}
-
-template <class T>
- consteval auto push_object(T const& object) -> void {
- (std::meta::reflect_object(object));
+ push |
---|
struct Deserializer {
-consteval auto pop() -> std::meta::info;
- consteval auto size() const -> size_t;
-
-template <class T>
- consteval auto pop_value() -> T {
- return extract<T>(pop());
- }
-
-template <class T>
- consteval auto pop_from_subobjects() -> T {
- ::vector<std::meta::info> rs;
- stdfor (std::meta::info _ : subobjects_of(^^T)) {
- .push_back(pop());
- rs}
+ namespace std::meta {
+
+class deserializer {
+<info> input; // exposition-only
+ vector
+public:
+explicit consteval deserializer(serializer const& s)
+ : input(s.output)
+ { }
+
+consteval auto pop() -> info {
+ auto r = input.back();
+ .pop_back();
+ inputreturn r;
+ }
-// proposed in this paper
- return std::meta::structural_cast<T>(rs);
+ consteval auto size() const -> size_t {
+ return input.size();
}
-// Equivalent to views::generate([this]{ return pop_value<T>(); })
- // except that we don't have a views::generate yet.
- // But the point is to be an input-only range of T
- template <class T>
- consteval auto into_range() -> unspecified;
- };
+template <class T>
+ consteval auto pop_value() -> T {
+ return extract<T>(pop());
+ }
+
+template <class T>
+ consteval auto pop_from_subobjects() -> T {
+ ::vector<info> rs;
+ stdfor (info _ : subobjects_of(^^T)) {
+ .push_back(pop());
+ rs}
+
+// proposed in this paper
+ return structural_cast<T>(rs);
+ }
+
+// Equivalent to:
+ // views::generate_n([this]{ return pop_value<T>(); }, size())
+ // except that we don't have a views::generate_n yet.
+ // But the point is to be an input-only range of T.
+ template <class T>
+ consteval auto into_range() -> unspecified;
+ };
+
+}
Which if we used the typed APIs @@ -1213,26 +1241,24 @@
And lastly the range-based API becomes simpler still:
@@ -1242,22 +1268,20 @@And this pattern works just as well for std::vector<T>
,
@@ -1271,24 +1295,23 @@
One additional point of interest here is that, while this didn’t @@ -1314,24 +1337,22 @@
But having to manually serialize and deserialize all the elements of @@ -1343,33 +1364,31 @@
Cool. This is… a lot. Not only is it a lot of decidedly non-trivial @@ -1401,35 +1420,31 @@
But… doing subobject-wise serialization is the default, right? So @@ -1441,24 +1456,19 @@
And, furthermore, if
-to_meta_representation
ends up
-serializing the correct number of reflections of the appropriate type,
-then from_meta_representation
can
-really be implicitly defaulted too.
Putting everything together, here is the proposed usage of this
facility for opting Optional
,
@@ -1472,57 +1482,57 @@
Tuple
simply have to default a
-single function. Vector
and
+and Tuple
simply have to default two
+functions. Vector
and
SmallString
have a single-line
serializer and a fairly short deserializer.
+ std::meta::serializer sa, sb; + a.to_meta_representation(sa); + b.to_meta_representation(sb); + return sa.output == sb.output; +}consteval auto template_argument_equivalent(C const& a, C const& b) -> bool { -struct Serializer { - ::vector<std::meta::info> output; - std -consteval auto push(std::meta::info r) -> void { - .push_back(r); - output} - -// ... rest of API ... - }; - - - Serializer sa, sb;.to_meta_representation(sa); - a.to_meta_representation(sb); - breturn sa.output == sb.output; - }
That is, two values are template-argument-equivalent if they @@ -1579,20 +1579,17 @@
-const C object = []{ auto c = C(init); - auto serializer = make-serializer(); - .to_meta_representation(serializer); + cauto s = std::meta::serializer(); + .to_meta_representation(s); c -auto deserializer = make-deserializer-from(serializer); - return C::from_meta_representation(deserializer); + auto ds = std::meta::deserializer(s); + return C::from_meta_representation(ds); }();
The actual types of the (de)serializer objects are -implementation-defined, the only thing that matters is that they conform -to the interface above. Note that, as a sanity check, the implementation -could do a second serialization round after the round-trip, do -ensure that both the original and new values produce the same -serialization.
+Note that, as a sanity check, the implementation could do a +second serialization round after the round-trip, do ensure that +both the original and new values produce the same serialization.
We will also have to adjust std::meta::reflect_value()
to also do this normalization. Which means:
-+template <class S> -consteval auto to_meta_representation(S&) const -> void;
consteval auto to_meta_representation(std::meta::serializer&) const -> void;
This function can also be declared as defaulted, in which case every @@ -1645,24 +1642,24 @@
A class type T
can also provide a
-from_meta_representation
that must
+
If a class type T
provides a
+to_meta_representation
, it must also
+then provide a
+from_meta_representation
, that must
be of the form:
-+template <class D> -consteval auto from_meta_representation(D&) -> T;
consteval auto from_meta_representation(std::meta::deserializer&) -> T;
If this function is not provided, it is implicitly defaulted. The -defaulted version of the function is:
+This function can also be declared as defaulted. The default +implementation is:
-+template <class D> -consteval auto from_meta_representation(D& deserializer) -> T { -assert(deserializer.size() == subobjects_of(^^T).size()); - return deserializer.template pop_from_subobjects<T>(); - }
consteval auto from_meta_representation(std::meta::deserializer& ds) -> T { +assert(ds.size() == subobjects_of(^^T).size()); + return ds.template pop_from_subobjects<T>(); + }
Second, we introduce the concept of template argument normalization @@ -1679,11 +1676,14 @@
v
be
-S + O
, where
-S
is that string literal and
-O
is some non-negative offset. Then
-v
is normalized to define_static_string(S) + O
-([P3491R0]).
+S + O
(or
+S[O]
,
+for a reference), where S
is that
+string literal and O
is some
+non-negative offset. Then v
is
+normalized to define_static_string(S) + O
+(or define_static_string(S)[O]
,
+for a reference). See [P3491R0].
T
is a scalar type or
an lvalue reference type, nothing is done.consteval auto NORMALIZE(T const& v) -> T {
-auto s = std::meta::make_serializer();
+ auto s = std::meta::serializer();
.to_meta_representation(s);
- vauto d = std::meta::make_deserializer_from(s);
- return T::from_meta_representation(d);
+ auto ds = std::meta::deserializer(s);
+ return T::from_meta_representation(ds);
}
(4.2)
Otherwise, every subobject of v
is
@@ -1824,7 +1824,7 @@
s1
and
s2
populated as follows:
-::meta::serializer s1, s2;
+ std::meta::serializer s1, s2;
std.to_meta_representation(s1);
v1.to_meta_representation(s2); v2
@@ -1842,12 +1842,13 @@ 4.2[LWG3354] requested).
Add a defaulted
-to_meta_representation
(that is, the
-default subobject-wise serialization with no normalization):
+to_meta_representation
and
+from_meta_representation
(that is,
+the default subobject-wise serialization with no normalization):
-template<class S>
-consteval void to_meta_representation(S&) = default;
+void to_meta_representation(std::meta::serializer&) = default;
+static auto from_meta_representation(std::meta::deserializer&) = default;
to all of the following library types, suitably constrained:
@@ -1891,11 +1892,11 @@ 4.2 consteval T structural_cast(R&&);
And introduce unspecified serializer and deserializer types:
+And introduce the serializer and deserializer types:
+consteval auto pop_from_subobjects() -> T { + ::vector<std::meta::info> rs; + stdfor (std::meta::info _ : subobjects_of(^^T)) { + .push_back(pop()); + rs} + +// proposed in this paper + return std::meta::structural_cast<T>(rs); + } + +// Equivalent to: + // views::generate_n([this]{ return pop_value<T>(); }, size()) + // except that we don't have a views::generate_n yet. + // But the point is to be an input-only range of T + template <class T> + consteval auto into_range() -> unspecified; + }; + }namespace std::meta { -class serializer // exposition-only + class serializer { <info> output; // exposition-only vector @@ -1936,37 +1937,49 @@
4.2 } }; -struct deserializer // exposition-only + class deserializer { - consteval auto pop() -> std::meta::info; - consteval auto size() const -> size_t; - -template <class T> - consteval auto pop_value() -> T { - return extract<T>(pop()); - } - -template <class T> - consteval auto pop_from_subobjects() -> T { - ::vector<std::meta::info> rs; - stdfor (std::meta::info _ : subobjects_of(^^T)) { - .push_back(pop()); - rs} - -// proposed in this paper - return std::meta::structural_cast<T>(rs); - } - -// Equivalent to views::generate_n([this]{ return pop_value<T>(); }, size()) - // except that we don't have a views::generate_n yet. - // But the point is to be an input-only range of T + <info> input; // exposition-only + vector +public: + explicit consteval deserializer(const serializer& s) + : input(s.output) + { } + +consteval auto pop() -> std::meta::info { + = input.back(); + info r .pop_back(); + inputreturn r; + } + +consteval auto size() const -> size_t { + return input.size(); + } + +template <class T> + consteval auto pop_value() -> T { + return extract<T>(pop()); + } + template <class T> - consteval auto into_range() -> unspecified; - }; - -consteval auto make_serializer() -> serializer; - consteval auto make_deserializer_from(serializer) -> deserializer; - }