Home > Mobile >  compile-time variable-length objects based on string
compile-time variable-length objects based on string

Time:05-28

Related SO questions:

Sadly neither one (or other similar ones) provide the solution I'm looking for.

Background

USB descriptors are (generally) byte-array structures. A "string descriptor" is defined as an array of bytes, that begins with a standard "header" of 2 bytes, followed by a string of UNICODE (16-bit) characters.

For example a USB string descriptor of value "AB" would have the following sequence of bytes:

0x06 0x03 0x41 0x00 0x42 0x00

where 0x06 is the total size of the descriptor (including the header), 0x03 is its "type" (defined by the standard)

Current (unsatisfactory) approach:

// other types omitted for clarity
enum UsbDescriptorType: uint8_t { USB_DESCR_STRING = 0x03 };

struct UsbDescrStd {
    uint8_t bLength;
    UsbDescriptorType bDescriptorType;
};

template<size_t N>
struct UsbDescrString final: UsbDescrStd {
    char str[N * 2];

    constexpr UsbDescrString(const char s[N]) noexcept
        : UsbDescrStd{sizeof(*this), UsbDescriptorType::USB_DESCR_STRING}
        , str {}
    {
        for(size_t i = 0; i < N;   i)
            str[i * 2] = s[i];
    }
};

Below are the examples of its usage and short comments on why they are "not good enough" for me:

// requires size information
constexpr UsbDescrString<9> uds9{"Descr str"};

// string duplication
constexpr UsbDescrString<sizeof("Descr str")-1> udsa{"Descr str"};

// requires an explicit string storage
constexpr auto UsbDescrStrTxt{"Descr str"};
constexpr UsbDescrString<sizeof(UsbDescrStrTxt)-1> udsa2{UsbDescrStrTxt};

// ugly use of a macro
#define MAKE_UDS(name, s) UsbDescrString<sizeof(s)-1> name{s}
constexpr MAKE_UDS(udsm, "Descr str");

"String argument to template" is explicitly prohibited as of C 20, cutting that solution off as well.

What I'm trying to achieve

Ideally I'd love to be able to write code like the following:

constexpr UsbDescrString uds{"Descr str"}; // or a similar "terse" approach

It is simple, terse, error-resistant, and to the point. And I need help writing my UsbDescrString in a way that allows me to create compile-time objects without unnecessary code bloat.

CodePudding user response:

Adding a CTAD to UsbDescrString should be enough

template<size_t N>
struct UsbDescrString final: UsbDescrStd {
    char str[N * 2];

    constexpr UsbDescrString(const char (&s)[N 1]) noexcept
        : UsbDescrStd{sizeof(*this), UsbDescriptorType::USB_DESCR_STRING}
        , str {}
    {
        for(size_t i = 0; i < N;   i)
            str[i * 2] = s[i];
    }
};

template<size_t N>
UsbDescrString(const char (&)[N]) -> UsbDescrString<N-1>;

Note that in order to prevent array to pointer decay, const char (&) needs to be used as the constructor parameter.

Demo

"String argument to template" is explicitly prohibited as of C 20, cutting that solution off as well.

However, thanks to P0732, with the help of some helper classes such as basic_fixed_string, now in C 20 you can

template<fixed_string>
struct UsbDescrString final: UsbDescrStd;

constexpr UsbDescrString<"Descr str"> uds9;
  • Related