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