# string_constant An `sc::string_constant` is a compile-time string in the same way that `std::integral_constant` is a compile-time integral value. When firmware needs to emit a log message, an integral ID is emitted instead of the string data. This conserves both runtime and storage resources. When decoding the log messages, a catalog file is needed to map the IDs back to their string values. There are two challenges with such a system. The first is assigning a unique ID to each string value and the second is how to support rich string operations like format and concatenation. The `sc::string_constant` library solves both of these challenges by representing the value of the string in its type. The `sc::string_constant`'s string template struct is parameterized by a list of chars representing the string value as well as a tuple of zero or more dynamic arguments to be formatted by a log decoder. The details of this implementation are provided in the Theory of Operation section. [![Embedded Logging Case Study: From C to Shining C++ - Luke Valenty -CppNow 2022](https://img.youtube.com/vi/Dt0vx-7e_B0/0.jpg)](https://www.youtube.com/watch?v=Dt0vx-7e_B0) ## Usage ### `_sc` user defined literal `sc::string_constant` uses a user-defined literal to make it easy to define a string: ```c++ constexpr auto hello_world = "Hello, World!"_sc; ``` Note how the auto keyword is used for the string type. This is necessary because the string's type changes depending on its value. It would be redundant and burdensome to specify the exact type. Also note the `constexpr` keyword is used. `sc::string_constant`s are fully `constexpr` and nearly all operations are performed at compile-time even without the `constexpr` keyword. Because the string value is represented by its type, strings can be assigned as type aliases or passed in as template parameters. ```c++ using HelloWorldType = decltype("Hello, World!"_sc); constexpr HelloWorldType helloWorld{}; static_assert(helloWorld == "Hello, World!"_sc); ``` ### Concatenation Two strings can be joined together using the `+` operator: ```c++ static_assert("Hi "_sc + "Emily!"_sc == "Hi Emily!"_sc); ``` ### Format `sc::string_constant`s can be formatted using a subset of python or `fmt::format` format specifiers. Formatting behavior is different depending on whether the arguments are compile-time values or dynamic values. Compile-time values will be formatted at compile time while dynamic values will only be formatted by tools outside of firmware like a log decoder. ```c++ constexpr auto my_age = 6; constexpr auto day_to_go = day_of_week::SATURDAY; auto my_name = "Olivia"_sc; using BirthdayParty = party; // use int_ to format an integer known at compile time static_assert( format("I am {} years old."_sc, sc::int_) == "I am 6 years old."_sc); // use enum_ to format an enum value known at compile time static_assert( format("Let's go on {}."_sc, sc::enum_) == "Let's go on SATURDAY."_sc); // strings can be used as format arguments as well static_assert( format("My name is {}."_sc, my_name) == "My name is Olivia."_sc); // use type_ to get the string of a type which can then be used as a format argument static_assert( type_ == "party"_sc); // multiple arguments can be formatted at once static_assert( format("My name is {} and I am {} years old."_sc, my_name, my_age) == "My name is Olivia and I am 6 years old."_sc); // runtime arguments not known at compile-time can also be formatted. the dynamic values can be // accessed at runtime and emitted along with the string_constant id. void read_memory(std::uint32_t addr) { auto const my_message = format("Reading memory at {:08x}"_sc, addr); } // both runtime and compile time arguments can be used in a single format operation template void readReg() { RegType reg{}; auto const reg_value = apply(read(reg.raw())); auto const my_message = format("Register {} = {}"_sc, type_, reg_value); } ``` Note that values known at compile time are passed into template variables as template parameters. This allows the string.format method to access constexpr versions of the values and format them into the string. If you format integers or enums without using the template variables, then the formatting happens outside of firmware by the log decoder collateral. ## Theory of Operation Compile-time string literals are created using user-defined string template literals. These are not yet part of a C++ standard but are supported by GCC, Clang, and MSVC. They allow for easy conversion of a string literal to template parameters. ```c++ template constexpr auto operator""_sc() { return sc::string_constant{}; } ``` We can see clearly that the string value is encoded in the type. ```c++ // create a compile-time string. note that it does not need to be constexpr or even const because the value is encoded in the type. auto hello_value = "Hello!"_sc; using HelloType = decltype(hello_value); // the string's value is represented by its type using ExpectedType = sc::string_constant; ExpectedType expected_value{}; // same type static_assert(std::is_same_v); // same value static_assert(hello_value == expected_value); ``` Encoding the string value in the type is necessary for string_constant support. Type information is used when linking object files together. This means the string values can be present in object files to be extracted during the build process. The other part is assigning a unique ID to each string. This can be done with the declaration of a template function with external linkage like this: ```c++ template extern std::uint32_t get_string_id(StringType); ``` When a string needs to be emitted as part of a log message, the `get_string_id()` function is used to translate the string type to its ID. During compilation, an intermediate object file is generated missing a definition for each instantiation of the `get_string_id()` function. This information can be extracted using the 'nm' tool and the original string values recovered. A new `strings.cpp` file can be generated that implements all the template instantiations returning a unique ID for each string. When the original object binary is linked to `strings.o`, the calls to `get_string_id()` can be replaced with the actual int value.