xref: /aosp_15_r20/external/emboss/doc/cpp-guide.md (revision 99e0aae7469b87d12f0ad23e61142c2d74c1ef70)
1*99e0aae7SDavid Rees# Emboss C++ User Guide
2*99e0aae7SDavid Rees
3*99e0aae7SDavid Rees[TOC]
4*99e0aae7SDavid Rees
5*99e0aae7SDavid Rees## General Principles
6*99e0aae7SDavid Rees
7*99e0aae7SDavid ReesIn C++, Emboss generates *view* classes which *do not* take ownership of any
8*99e0aae7SDavid Reesdata.  Application code is expected to manage the actual binary data.  However,
9*99e0aae7SDavid ReesEmboss views are extremely cheap to construct (often free when optimizations are
10*99e0aae7SDavid Reesturned on), so it is expected that applications can pass around pointers to
11*99e0aae7SDavid Reesbinary data and instantiate views as needed.
12*99e0aae7SDavid Rees
13*99e0aae7SDavid ReesAll of the generated C++ code is in templates, so only code that is actually
14*99e0aae7SDavid Reescalled will be linked into your application.
15*99e0aae7SDavid Rees
16*99e0aae7SDavid ReesUnless otherwise noted, all code for a given Emboss module will be generated in
17*99e0aae7SDavid Reesthe namespace given by the module's `[(cpp) namespace]` attribute.
18*99e0aae7SDavid Rees
19*99e0aae7SDavid Rees
20*99e0aae7SDavid Rees### Read-Only vs Read-Write vs C++ `const`
21*99e0aae7SDavid Rees
22*99e0aae7SDavid ReesEmboss views can be applied to read-only or read-write storage:
23*99e0aae7SDavid Rees
24*99e0aae7SDavid Rees```c++
25*99e0aae7SDavid Reesvoid CopyX(const std::vector<char> &src, std::vector<char> *dest) {
26*99e0aae7SDavid Rees  auto source_view = MakeXView(&src);
27*99e0aae7SDavid Rees  auto dest_view = MakeXView(dest);
28*99e0aae7SDavid Rees  dest_view.x().Write(source_view.x().Read());
29*99e0aae7SDavid Rees}
30*99e0aae7SDavid Rees```
31*99e0aae7SDavid Rees
32*99e0aae7SDavid ReesWhen applied to read-only storage, methods like `Write()` or
33*99e0aae7SDavid Rees`UpdateFromTextStream()` won't compile:
34*99e0aae7SDavid Rees
35*99e0aae7SDavid Rees```c++
36*99e0aae7SDavid Reesvoid WontCompile(const std::vector<char> &data) {
37*99e0aae7SDavid Rees  auto view = MakeXView(&data);
38*99e0aae7SDavid Rees  view.x().Write(10);  // Won't compile.
39*99e0aae7SDavid Rees}
40*99e0aae7SDavid Rees```
41*99e0aae7SDavid Rees
42*99e0aae7SDavid ReesThis is separate from the C++ `const`ness of the view itself!  For example, the
43*99e0aae7SDavid Reesfollowing will work with no issue:
44*99e0aae7SDavid Rees
45*99e0aae7SDavid Rees```c++
46*99e0aae7SDavid Reesvoid WillCompileAndRun(std::vector<char> *data) {
47*99e0aae7SDavid Rees  const auto view = MakeXView(&data);
48*99e0aae7SDavid Rees  view.x().Write(10);
49*99e0aae7SDavid Rees}
50*99e0aae7SDavid Rees```
51*99e0aae7SDavid Rees
52*99e0aae7SDavid ReesThis works because views are like pointers.  In C++, you can have any
53*99e0aae7SDavid Reescombination of `const`/non-`const` pointer to `const`/non-`const` data:
54*99e0aae7SDavid Rees
55*99e0aae7SDavid Rees```c++
56*99e0aae7SDavid Reeschar *             ncnc;  // Pointer is mutable, and points to mutable data.
57*99e0aae7SDavid Reesconst char *       ncc;   // Point is mutable, but points to const data.
58*99e0aae7SDavid Reeschar const *       ncc2;  // Another way of writing const char *
59*99e0aae7SDavid Reeschar *const        cnc;   // Pointer is constant, but points to mutable data.
60*99e0aae7SDavid Reesusing char_p = char *;
61*99e0aae7SDavid Reesconst char_p       cnc2;  // Another way of writing char *const
62*99e0aae7SDavid Reesconst char *const  cc;    // Pointer is constant, and points to constant data.
63*99e0aae7SDavid Reesusing c_char_p = const char *;
64*99e0aae7SDavid Reesconst c_char_p *   cc2;   // Another way of writing const char *const
65*99e0aae7SDavid Rees```
66*99e0aae7SDavid Rees
67*99e0aae7SDavid ReesThe Emboss view equivalents are:
68*99e0aae7SDavid Rees
69*99e0aae7SDavid Rees```c++
70*99e0aae7SDavid ReesGenericMyStructView<ContiguousBuffer<char, ...>>              ncnc;
71*99e0aae7SDavid ReesGenericMyStructView<ContiguousBuffer<const char, ...>>        ncc;
72*99e0aae7SDavid ReesGenericMyStructView<ContiguousBuffer<char const, ...>>        ncc2;
73*99e0aae7SDavid ReesGenericMyStructView<ContiguousBuffer<char, ...>> const        cnc;
74*99e0aae7SDavid Reesconst GenericMyStructView<ContiguousBuffer<char, ...>>        cnc2;
75*99e0aae7SDavid ReesGenericMyStructView<ContiguousBuffer<const char, ...>> const  cc;
76*99e0aae7SDavid Reesconst GenericMyStructView<ContiguousBuffer<const char, ...>>  cc2;
77*99e0aae7SDavid Rees```
78*99e0aae7SDavid Rees
79*99e0aae7SDavid ReesFor this reason, `const` methods of views work on `const` *views*, not
80*99e0aae7SDavid Reesnecessarily on `const` data: for example, `UpdateFromTextStream()` is a `const`
81*99e0aae7SDavid Reesmethod, because it does not modify the view itself, but it will not work if the
82*99e0aae7SDavid Reesview points to `const` data.  This is analogous to writing through a constant
83*99e0aae7SDavid Reespointer, like: `char *const p = &some_char; *p = 'z';`.
84*99e0aae7SDavid Rees
85*99e0aae7SDavid ReesConversely, non-`const` methods, such as `operator=`, still work on views of
86*99e0aae7SDavid Rees`const` data.  This is analogous to `pointer_to_const_char =
87*99e0aae7SDavid Reesother_pointer_to_const_char`.
88*99e0aae7SDavid Rees
89*99e0aae7SDavid Rees
90*99e0aae7SDavid Rees## Example: Fixed-Size `struct`
91*99e0aae7SDavid Rees
92*99e0aae7SDavid ReesGiven a simple, fixed-size `struct`:
93*99e0aae7SDavid Rees
94*99e0aae7SDavid Rees```
95*99e0aae7SDavid Rees[(cpp) namespace = "example"]
96*99e0aae7SDavid Rees
97*99e0aae7SDavid Reesstruct MyStruct:
98*99e0aae7SDavid Rees  0 [+4]  UInt  field_a
99*99e0aae7SDavid Rees  4 [+4]  Int   field_b
100*99e0aae7SDavid Rees  8 [+4]  Bcd   field_c
101*99e0aae7SDavid Rees```
102*99e0aae7SDavid Rees
103*99e0aae7SDavid ReesEmboss will generate code with this public C++ interface:
104*99e0aae7SDavid Rees
105*99e0aae7SDavid Rees```c++
106*99e0aae7SDavid Reesnamespace example {
107*99e0aae7SDavid Rees
108*99e0aae7SDavid Rees// The view class for the struct.  Views are like pointers: they do not own
109*99e0aae7SDavid Rees// their storage.
110*99e0aae7SDavid Rees//
111*99e0aae7SDavid Rees// `Storage` is typically some ::emboss::support::ContiguousBuffer (which uses
112*99e0aae7SDavid Rees// contiguous memory as backing storage), but you would typically just use
113*99e0aae7SDavid Rees// `auto`:
114*99e0aae7SDavid Rees//
115*99e0aae7SDavid Rees//     auto view = MakeMyStructView(&container);
116*99e0aae7SDavid Rees//
117*99e0aae7SDavid Rees// If you need to make a view of some non-RAM backing storage (e.g., a register
118*99e0aae7SDavid Rees// file on a remote device, accessed via SPI), you can provide your own Storage.
119*99e0aae7SDavid Reestemplate <class Storage>
120*99e0aae7SDavid Reesclass GenericMyStructView final {
121*99e0aae7SDavid Rees public:
122*99e0aae7SDavid Rees  // Typically, you do not need to explicitly call any of the constructors.
123*99e0aae7SDavid Rees
124*99e0aae7SDavid Rees  // The default constructor gives you a "null" view: you cannot read or write
125*99e0aae7SDavid Rees  // through the view, Ok() and IsComplete() return false, and so on.
126*99e0aae7SDavid Rees  GenericMyStructView();
127*99e0aae7SDavid Rees
128*99e0aae7SDavid Rees  // A non-"null" view must be constructed with an appropriate Storage.
129*99e0aae7SDavid Rees  explicit GenericMyStructView(Storage bytes);
130*99e0aae7SDavid Rees
131*99e0aae7SDavid Rees  // Views can be copy-constructed and assigned from views of "compatible"
132*99e0aae7SDavid Rees  // Storage.  For ContiguousBuffer, that means ContiguousBuffer over any of the
133*99e0aae7SDavid Rees  // char types -- char, unsigned char, and signed char.  std::uint8_t and
134*99e0aae7SDavid Rees  // std::int8_t are typically aliases of char types, but are not required to
135*99e0aae7SDavid Rees  // be by the C++ standard.
136*99e0aae7SDavid Rees  template <typename OtherStorage>
137*99e0aae7SDavid Rees  GenericMyStructView(const GenericMyStructView<OtherStorage> &other);
138*99e0aae7SDavid Rees
139*99e0aae7SDavid Rees  template <typename OtherStorage>
140*99e0aae7SDavid Rees  GenericMyStructView<Storage> &operator=(
141*99e0aae7SDavid Rees      const GenericMyStructView<OtherStorage> &other);
142*99e0aae7SDavid Rees
143*99e0aae7SDavid Rees
144*99e0aae7SDavid Rees  // Ok() returns true if the Storage is big enough for the struct (for
145*99e0aae7SDavid Rees  // MyStruct, at least 12 bytes), and all fields are Ok().  For this struct,
146*99e0aae7SDavid Rees  // the Int and UInt fields are always Ok(), and the Bcd field is Ok() if none
147*99e0aae7SDavid Rees  // of its nibbles has a value greater than 9.
148*99e0aae7SDavid Rees  bool Ok() const;
149*99e0aae7SDavid Rees
150*99e0aae7SDavid Rees  // IsComplete() returns true if the Storage is big enough for the struct.
151*99e0aae7SDavid Rees  // This is most useful when you are reading bytes from some stream: you can
152*99e0aae7SDavid Rees  // read until IsComplete() is true, and then use IntrinsicSizeInBytes() to
153*99e0aae7SDavid Rees  // find out how many bytes are actually used by the struct, and Ok() to find
154*99e0aae7SDavid Rees  // out if the bytes are correct.
155*99e0aae7SDavid Rees  //
156*99e0aae7SDavid Rees  // An alternate way of thinking about it is: Ok() tells you if you can read a
157*99e0aae7SDavid Rees  // structure; IsComplete() tells you if you can write to it.
158*99e0aae7SDavid Rees  bool IsComplete() const;
159*99e0aae7SDavid Rees
160*99e0aae7SDavid Rees
161*99e0aae7SDavid Rees  // The Equals() and UncheckedEquals() methods check if two structs are
162*99e0aae7SDavid Rees  // *logically* equal.  Equals() performs Ok() and bounds checks,
163*99e0aae7SDavid Rees  // UncheckedEquals() does not: UncheckedEquals() is useful when you need
164*99e0aae7SDavid Rees  // maximum performance, and can guarantee that your structures are Ok()
165*99e0aae7SDavid Rees  // before calling UncheckedEquals().
166*99e0aae7SDavid Rees  template <typename OtherStorage>
167*99e0aae7SDavid Rees  bool Equals(GenericMyStructView<OtherStorage> other) const;
168*99e0aae7SDavid Rees  template <typename OtherStorage>
169*99e0aae7SDavid Rees  bool UncheckedEquals(GenericMyStructView<OtherStorage> other) const;
170*99e0aae7SDavid Rees
171*99e0aae7SDavid Rees  // CopyFrom() and UncheckedCopyFrom() copy the bytes of the source structure
172*99e0aae7SDavid Rees  // directly from its Storage.  CopyFrom() performs bounds checks to ensure
173*99e0aae7SDavid Rees  // that there are enough bytes available in the source; UncheckedCopyFrom()
174*99e0aae7SDavid Rees  // does not.  With ContiguousBuffer storage, these should have essentially
175*99e0aae7SDavid Rees  // identical performance to memcpy().
176*99e0aae7SDavid Rees  template <typename OtherStorage>
177*99e0aae7SDavid Rees  void CopyFrom(GenericMyStructView<OtherStorage> other) const;
178*99e0aae7SDavid Rees  template <typename OtherStorage>
179*99e0aae7SDavid Rees  void UncheckedCopyFrom(GenericMyStructView<OtherStorage> other) const;
180*99e0aae7SDavid Rees
181*99e0aae7SDavid Rees
182*99e0aae7SDavid Rees  // UpdateFromTextStream() attempts to update the structure from text format.
183*99e0aae7SDavid Rees  // The Stream class provides a simple interface for getting and ungetting
184*99e0aae7SDavid Rees  // characters; typically, you would use ::emboss::UpdateFromText(view,
185*99e0aae7SDavid Rees  // some_string) instead of calling this yourself.
186*99e0aae7SDavid Rees  template <class Stream>
187*99e0aae7SDavid Rees  bool UpdateFromTextStream(Stream *stream) const;
188*99e0aae7SDavid Rees
189*99e0aae7SDavid Rees  // WriteToTextStream() writes a textual representation of the structure to the
190*99e0aae7SDavid Rees  // provided stream.  Typically, you would use ::emboss::WriteToString(view)
191*99e0aae7SDavid Rees  // instead.
192*99e0aae7SDavid Rees  template <class Stream>
193*99e0aae7SDavid Rees  void WriteToTextStream(Stream *stream,
194*99e0aae7SDavid Rees                         ::emboss::TextOutputOptions options) const;
195*99e0aae7SDavid Rees
196*99e0aae7SDavid Rees
197*99e0aae7SDavid Rees  // Each field in the struct will have a method to get its corresponding view.
198*99e0aae7SDavid Rees  //
199*99e0aae7SDavid Rees  // The exact types of the returned views are not contractual.
200*99e0aae7SDavid Rees  ::emboss::prelude::UIntView<...> field_a() const;
201*99e0aae7SDavid Rees  ::emboss::prelude::IntView<...> field_b() const;
202*99e0aae7SDavid Rees  ::emboss::prelude::BcdView<...> field_c() const;
203*99e0aae7SDavid Rees
204*99e0aae7SDavid Rees
205*99e0aae7SDavid Rees  // The built-in virtual fields also have methods to get their views:
206*99e0aae7SDavid Rees  // $size_in_bytes has IntrinsicSizeInBytes(), $max_size_in_bytes has
207*99e0aae7SDavid Rees  // MaxSizeInBytes(), and $min_size_in_bytes has MinSizeInBytes().
208*99e0aae7SDavid Rees  //
209*99e0aae7SDavid Rees  // Because $min_size_in_bytes and $max_size_in_bytes are always constant,
210*99e0aae7SDavid Rees  // their corresponding field methods are always static constexpr.  Because
211*99e0aae7SDavid Rees  // $size_in_bytes is also constant for MyStruct, IntrinsicSizeInBytes() will
212*99e0aae7SDavid Rees  // also be static constexpr for GenericMyStructView:
213*99e0aae7SDavid Rees  //
214*99e0aae7SDavid Rees  // For any virtual field, you can use its Ok() method to find out if you can
215*99e0aae7SDavid Rees  // Read() its value:
216*99e0aae7SDavid Rees  //
217*99e0aae7SDavid Rees  //     if (view.IntrinsicSizeInBytes().Ok()) {
218*99e0aae7SDavid Rees  //       // The size of the struct is known.
219*99e0aae7SDavid Rees  //       DoSomethingWithNBytes(view.IntrinsicSizeInBytes().Read());
220*99e0aae7SDavid Rees  //     }
221*99e0aae7SDavid Rees  //
222*99e0aae7SDavid Rees  // For constant values, Ok() will always return true.
223*99e0aae7SDavid Rees  //
224*99e0aae7SDavid Rees  // For MyStruct, my_struct_view.IntrinsicSizeInBytes().Read(),
225*99e0aae7SDavid Rees  // my_struct_view.MinSizeInBytes().Read(), and
226*99e0aae7SDavid Rees  // my_struct_view.MaxSizeInBytes().Read() will all return 12.
227*99e0aae7SDavid Rees  //
228*99e0aae7SDavid Rees  // For constexpr fields, you can also get their values from functions in the
229*99e0aae7SDavid Rees  // structure's namespace, which also lets you skip the Read():
230*99e0aae7SDavid Rees  //
231*99e0aae7SDavid Rees  //     MyStruct::IntrinsicSizeInBytes()
232*99e0aae7SDavid Rees  //     MyStruct::MaxSizeInBytes()
233*99e0aae7SDavid Rees  //     MyStruct::MinSizeInBytes()
234*99e0aae7SDavid Rees  static constexpr IntrinsicSizeInBytesView IntrinsicSizeInBytes();
235*99e0aae7SDavid Rees  static constexpr MinSizeInBytesView MinSizeInBytes();
236*99e0aae7SDavid Rees  static constexpr MaxSizeInBytesView MaxSizeInBytes();
237*99e0aae7SDavid Rees
238*99e0aae7SDavid Rees  // The IntrinsicSizeInBytes() method returns the view of the $size_in_bytes
239*99e0aae7SDavid Rees  // virtual field.  Because $size_in_bytes is constant, this is a static
240*99e0aae7SDavid Rees  // constexpr method.
241*99e0aae7SDavid Rees  //
242*99e0aae7SDavid Rees  // Typically, you would use IntrinsicSizeInBytes().Ok() and
243*99e0aae7SDavid Rees  // IntrinsicSizeInBytes().Read():
244*99e0aae7SDavid Rees  //
245*99e0aae7SDavid Rees  //   if (view.IntrinsicSizeInBytes().Ok()) {
246*99e0aae7SDavid Rees  //     // The size of the struct is known.
247*99e0aae7SDavid Rees  //     DoSomethingWithNBytes(view.IntrinsicSizeInBytes().Read());
248*99e0aae7SDavid Rees  //   }
249*99e0aae7SDavid Rees  //
250*99e0aae7SDavid Rees  // Because MyStruct is always 12 bytes,
251*99e0aae7SDavid Rees  // GenericMyStructView::IntrinsicSizeInBytes().Ok() will always be true.
252*99e0aae7SDavid Rees  static constexpr UIntView<...> IntrinsicSizeInBytes();
253*99e0aae7SDavid Rees
254*99e0aae7SDavid Rees  // If you need to get at the raw bytes underneath the view, you can get the
255*99e0aae7SDavid Rees  // view's Storage.
256*99e0aae7SDavid Rees  Storage BackingStorage() const;
257*99e0aae7SDavid Rees};
258*99e0aae7SDavid Rees
259*99e0aae7SDavid Rees
260*99e0aae7SDavid Rees// An overload of MakeMyStructView is provided which accepts a pointer to a
261*99e0aae7SDavid Rees// container type: this generally works with STL and STL-like containers of
262*99e0aae7SDavid Rees// chars, that have size() and data() methods.  This is known to work with
263*99e0aae7SDavid Rees// std::vector<char>, std::array<char>, std::string, absl:: and
264*99e0aae7SDavid Rees// std::string_view, and some others.  Note that you need to call this with a
265*99e0aae7SDavid Rees// pointer to the container:
266*99e0aae7SDavid Rees//
267*99e0aae7SDavid Rees//     auto view = MakeMyStructView(&container);
268*99e0aae7SDavid Rees//
269*99e0aae7SDavid Rees// IMPORTANT: this does *not* keep a reference to the actual container, so if
270*99e0aae7SDavid Rees// you call a container method that invalidates data() (such as
271*99e0aae7SDavid Rees// std::vector<>::reserve()), you will have to make a new view.
272*99e0aae7SDavid Reestemplate <typename Container>
273*99e0aae7SDavid Reesinline GenericMyStructView<...> MakeMyStructView(Container *arg);
274*99e0aae7SDavid Rees
275*99e0aae7SDavid Rees// Alternately, a "C-style" overload is provided, if you just have a pointer and
276*99e0aae7SDavid Rees// length:
277*99e0aae7SDavid Reestemplate <typename CharType>
278*99e0aae7SDavid Reesinline GenericMyStructView<...> MakeMyStructView(CharType *buffer,
279*99e0aae7SDavid Rees                                                 std::size_t length);
280*99e0aae7SDavid Rees
281*99e0aae7SDavid Rees
282*99e0aae7SDavid Rees// In addition to the View class, a namespace will be generated with the
283*99e0aae7SDavid Rees// compile-time constant elements of the class.  This is a convenience, so that
284*99e0aae7SDavid Rees// you can write something like:
285*99e0aae7SDavid Rees//
286*99e0aae7SDavid Rees//     std::array<char, MyStruct::IntrinsicSizeInBytes()>
287*99e0aae7SDavid Rees//
288*99e0aae7SDavid Rees// instead of:
289*99e0aae7SDavid Rees//
290*99e0aae7SDavid Rees//     std::array<char, GenericMyStructView<ContiguousBuffer<
291*99e0aae7SDavid Rees//                              char>>::IntrinsicSizeInBytes().Read()>
292*99e0aae7SDavid Reesnamespace MyStruct {
293*99e0aae7SDavid Rees
294*99e0aae7SDavid Rees// Because MyStruct only has some constant virtual fields, the namespace
295*99e0aae7SDavid Rees// MyStruct only contains a few corresponding functions.  Note that the
296*99e0aae7SDavid Rees// functions here return values, not views:
297*99e0aae7SDavid Reesinline constexpr unsigned int IntrinsicSizeInBytes();
298*99e0aae7SDavid Reesinline constexpr unsigned int MaxSizeInBytes();
299*99e0aae7SDavid Reesinline constexpr unsigned int MinSizeInBytes();
300*99e0aae7SDavid Rees
301*99e0aae7SDavid Rees}  // namespace MyStruct
302*99e0aae7SDavid Rees}  // namespace example
303*99e0aae7SDavid Rees```
304*99e0aae7SDavid Rees
305*99e0aae7SDavid Rees
306*99e0aae7SDavid Rees## TODO(bolms): Example: Variable-Size `struct`
307*99e0aae7SDavid Rees
308*99e0aae7SDavid Rees
309*99e0aae7SDavid Rees## TODO(bolms): Example: `enum`
310*99e0aae7SDavid Rees
311*99e0aae7SDavid Rees
312*99e0aae7SDavid Rees## TODO(bolms): Example: `bits`
313*99e0aae7SDavid Rees
314*99e0aae7SDavid Rees
315