lefticus / cpp_weekly

The official C++ Weekly Repository. Code samples and notes of future / past episodes will land here at various times. PR's will be accepted in some cases.
The Unlicense
700 stars 26 forks source link

User-defined literal for USB String Descriptors #267

Open yephick opened 1 year ago

yephick commented 1 year ago

Channel

C++Weekly

Topics

  1. user-defined literals that require both a template and a non-template arguments
  2. compile-time initializers for embedded devices' code
  3. low-level memory layout to adhere to external specifications
  4. following (or not?) naming conventions defined in reference standards (like the bLength and bDescriptorType from USB standard's text).

Length

Not sure, TBH. I've been trying to unsuccessfully solve this puzzle for a while now. Wouldn't dare claim I'm a guru, but not a novice in C++ either. My best guess is that the episode would be around 7-11 minutes.

Here's what the puzzle is:

USB string descriptors are defined as a "length" byte (total size of the descriptor) followed by a descriptor type's byte (0x03 for String) followed by UTF-16 characters. For example:

enum UsbDescriptorType : uint8_t{
    USB_DESCR_DEVICE        = 1
  , USB_DESCR_CONFIGURATION = 2
  , USB_DESCR_STRING        = 3
 // ... other types
};

struct UsbDescrStd{
    const uint8_t bLength;
    const UsbDescriptorType bDescriptorType;
};

template<size_t N>
struct UsbDescrString: UsbDescrStd{
    char16_t str[N-1]; // [N-1] to allow constructor from C-strings: UsbDescrString(const char(&s)[N])
};

Now... defining those USB strings in code is a bit cumbersome as it requires me to explicitly provide the size as template argument: constinit const UsbDescrString<6> usbStrManufacturer {"MyLtd"};

The goal is to provide a user-defined literal that would make that initialization look clean and natural, like this: constinit const auto usbStrManufacturer {"MyLtd"_usbstr};

The issue here is that I need to provide both template and non-template arguments into the user-defined literal, which is explicitly prohibited by the standard

YePhIcK (pronounced as "yefik")

P.S. This is, of course, code for an embedded device that adheres to USB device spec. Managing USB descriptors is a real nightmare so I'm trying to automate as much as possible to reduce or completely eliminate room for human mistakes

P.P.S. My attempt at solving this puzzle with the help of SO community: https://stackoverflow.com/questions/75051100/string-literals-as-template-arguments-forces-code-duplication-and-verbosity

and the best solution which I ended up NOT implementing because of the verbosity and complexity of the resulting code: https://godbolt.org/z/nGxKxK4q7

Neither my SO question nor the "solution" do the user-defined string literals, just linking them as "more info" type of thing

syntax-rules commented 1 year ago

here's an approach I came up with, requires C++20: https://godbolt.org/z/7bhqMYs3T It imposes an upper limit, but since you only have a length byte maybe that's ok.

yephick commented 1 year ago

here's an approach I came up with, requires C++20: https://godbolt.org/z/7bhqMYs3T It imposes an upper limit, but since you only have a length byte maybe that's ok.

I see what you've done here, quite concise. Unfortunately, while this may work for many other scenarios, it fails for this particular one where the actual byte size of the resulting struct MUST be the declared bLength as it is used in USB protocol where that matters.