@@ -23,9 +23,10 @@ class ValueField
2323 ValueField (Value&& value) : m_value(value) {}
2424 Value& m_value;
2525
26+ const Value& get () const { return m_value; }
2627 Value& get () { return m_value; }
2728 Value& init () { return m_value; }
28- bool has () { return true ; }
29+ bool has () const { return true ; }
2930};
3031
3132template <typename Accessor, typename Struct>
@@ -166,10 +167,52 @@ struct ReadDestUpdate
166167 Value& m_value;
167168};
168169
169- template <typename ... LocalTypes, typename ... Args>
170- decltype (auto ) ReadField(TypeList<LocalTypes...>, Args&&... args)
170+ // ! Return whether to read a C++ value from a Cap'n Proto field. Returning
171+ // ! false can be useful to interpret certain Cap'n Proto field values as null
172+ // ! C++ values when initializing nullable C++ std::optional / std::unique_ptr /
173+ // ! std::shared_ptr types.
174+ // !
175+ // ! For example, when reading from a `List(Data)` field into a
176+ // ! `std::vector<std::shared_ptr<const CTransaction>>` value, it's useful to be
177+ // ! able to interpret empty `Data` values as null pointers. This is useful
178+ // ! because the Cap'n Proto C++ API does not currently provide a way to
179+ // ! distinguish between null and empty Data values in a List[*], so we need to
180+ // ! choose some Data value to represent null if we want to allow passing null
181+ // ! pointers. Since no CTransaction is ever serialized as empty Data, it's safe
182+ // ! to use empty Data values to represent null pointers.
183+ // !
184+ // ! [*] The Cap'n Proto wire format actually does distinguish between null and
185+ // ! empty Data values inside Lists, and the C++ API does allow distinguishing
186+ // ! between null and empty Data values in other contexts, just not the List
187+ // ! context, so this limitation could be removed in the future.
188+ // !
189+ // ! Design note: CustomHasField() and CustomHasValue() are inverses of each
190+ // ! other. CustomHasField() allows leaving Cap'n Proto fields unset when C++
191+ // ! types have certain values, and CustomHasValue() allows leaving C++ values
192+ // ! unset when Cap'n Proto fields have certain values. But internally the
193+ // ! functions get called in different ways. This is because in C++, unlike in
194+ // ! Cap'n Proto not every C++ type is default constructible, and it may be
195+ // ! impossible to leave certain C++ values unset. For example if a C++ method
196+ // ! requires function parameters, there's no way to call the function without
197+ // ! constructing values for each of the parameters. Similarly there's no way to
198+ // ! add values to C++ vectors or maps without initializing those values. This
199+ // ! is not the case in Cap'n Proto where all values are optional and it's
200+ // ! possible to skip initializing parameters and list elements.
201+ // !
202+ // ! Because of this difference, CustomHasValue() works universally and can be
203+ // ! used to disable BuildField() calls in every context, while CustomHasField()
204+ // ! can only be used to disable ReadField() calls in certain contexts like
205+ // ! std::optional and pointer contexts.
206+ template <typename ... LocalTypes, typename Input>
207+ bool CustomHasField (TypeList<LocalTypes...>, InvokeContext& invoke_context, const Input& input)
208+ {
209+ return input.has ();
210+ }
211+
212+ template <typename ... LocalTypes, typename Input, typename ... Args>
213+ decltype (auto ) ReadField(TypeList<LocalTypes...>, InvokeContext& invoke_context, Input&& input, Args&&... args)
171214{
172- return CustomReadField (TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2 >(), std::forward<Args>(args)...);
215+ return CustomReadField (TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2 >(), invoke_context, std::forward<Input>(input), std::forward<Args>(args)...);
173216}
174217
175218template <typename LocalType, typename Input>
@@ -190,6 +233,13 @@ void ThrowField(TypeList<std::exception>, InvokeContext& invoke_context, Input&&
190233 throw std::runtime_error (std::string (CharCast (data.begin ()), data.size ()));
191234}
192235
236+ // ! Return whether to write a C++ value into a Cap'n Proto field. Returning
237+ // ! false can be useful to map certain C++ values to unset Cap'n Proto fields.
238+ // !
239+ // ! For example the bitcoin `Coin` class asserts false when a spent coin is
240+ // ! serialized. But some C++ methods return these coins, so there needs to be a
241+ // ! way to represent them in Cap'n Proto and a null Data field is a convenient
242+ // ! representation.
193243template <typename ... Values>
194244bool CustomHasValue (InvokeContext& invoke_context, const Values&... value)
195245{
@@ -372,7 +422,7 @@ struct ClientException
372422 void handleField (InvokeContext& invoke_context, Results& results, ParamList)
373423 {
374424 StructField<Accessor, Results> input (results);
375- if (input. has ( )) {
425+ if (CustomHasField (TypeList<Exception>(), invoke_context, input )) {
376426 ThrowField (TypeList<Exception>(), invoke_context, input);
377427 }
378428 }
0 commit comments