How to use a constexpr std::string

With C++20 we got consexpr std::vector and std::string in the STL. An awesome feature, I think, from a technical standpoint. However, the initial joy often ends quickly once most people I met tried to use it. Why? Well, maybe you already explored what you can do with this new ability of std::string and discovered that the most naive application, creating a string at compile-time and transferring the data into run-time, isn't supported. There was a proposal aiming to add this kind of support, but it didn't end up in the standard. That leaves us with a std::string we can create at compile-time, but we must also destroy the string at compile-time. It doesn't sound so good anymore, right?

Let's accept the fact that we can have that thing only at compile time, and we want to do something sensible with the new feature. What can we do? Here is one example.

Do you own a mobile phone? I come from an age where SMS was not only very expensive but also a cool new feature. Back then, I had to write a decode and encode for the SMS payload. Also, back then, bandwidth was expensive, too, and SMS was only a debug element providers started making money with; the number of characters was limited. In fact, it was so limited that the PDU used a clever trick. It was meant to be only for ASCII-supported languages. With that limitation, we all know that such a character fits into a char or 8-bit. However, looking more closely, only 7 of these 8 bits are used. The most significant bit is always 0. It is during these times that people start becoming creative. Let's use that 8th bit. How? Well, the 8th bit becomes the lowest bit from the following byte, which then carries the next two bits for the following byte and so forth. In the end, this encodes eight characters into 7 bytes. A brilliant save at the time.

Long story short, as I said in the beginning, I wrote such a decoder and encoder. For this post, let's only care about the decoder. I polished my well-aged code a bit, applying C++23 to it. Here is how the decode looks:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
constexpr std::string
decode(std::span<const unsigned char> payload)
{
  static constexpr unsigned char MASK{0x7F};
  std::string                    result{};

  int           shift{};
  unsigned char mask{MASK};
  for(unsigned char scratch{}; auto c : payload) {
    const unsigned char realChar =
      ((c & mask) << shift) | scratch;
    scratch = (c & (~mask)) >> (7 - shift);

    ++shift;
    mask >>= 1;

    result += static_cast<char>(realChar);

    if(7 == shift) {
      result += static_cast<char>(scratch);
      shift   = 0;
      mask    = MASK;
      scratch = 0;
    }
  }

  return result;
}

Remember, this post is about constexpr std::string. Let's focus on that instead of the decode algorithm.

Looking at the function declaration, you can see decode returns a std::string and the function itself is constexpr.

Book an in-house C++ training class

Do you like this content?

I'm available for in-house C++ training classes worldwide, on-site or remote. Here is a sample list of my classes:
  • From C to C++
  • Programming with C++11 to C++17
  • Programming with C++20
All classes can be customized to your team's needs. Training services

Sure, I agree; there are probably zero SMS you can decode during compile-time. Hmm, maybe there are. What about testing? You definitely want to test such an algorithm as well! We have unit tests for that. But

  1. they often run late
  2. they might not detect undefined behavior

Did I get your attention?

While we can't do as much as we want with a constexpr std::string, one thing we can do is check whether it matches another string in a static_assert:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
constexpr auto make_array(auto... values)
{
  return std::array<unsigned char, sizeof...(values)>{
    static_cast<unsigned char>(values)...};
}

void Use()
{
  constexpr auto payload{make_array(0xd3,
                                    0x74,
                                    0x1b,
                                    0xce,
                                    0x2e,
                                    0x83,
                                    0xa6,
                                    0xcd,
                                    0x29,
                                    0x88,
                                    0x5e,
                                    0xc6,
                                    0xd3,
                                    0x5d)};
  constexpr auto payload2{make_array(0xc8,
                                     0x32,
                                     0x9b,
                                     0xfd,
                                     0x66,
                                     0x81,
                                     0x86,
                                     0xab,
                                     0x55,
                                     0x08,
                                     0x44,
                                     0x45,
                                     0xa7,
                                     0xe7,
                                     0xa0,
                                     0xf4,
                                     0x1c,
                                     0x34,
                                     0x46,
                                     0x97,
                                     0xc7,
                                     0xeb,
                                     0xb4,
                                     0xfb,
                                     0x0c,
                                     0x0a,
                                     0x83,
                                     0xe6,
                                     0x74,
                                     0xb2,
                                     0x4e,
                                     0x37,
                                     0xa7,
                                     0xcb,
                                     0xd3,
                                     0xee,
                                     0x33,
                                     0x28,
                                     0x4c,
                                     0x07,
                                     0x8d,
                                     0xdf,
                                     0x6d,
                                     0x78,
                                     0x9a,
                                     0x5d,
                                     0x06,
                                     0xd1,
                                     0xd3,
                                     0xed,
                                     0x72,
                                     0x08,
                                     0xe4,
                                     0x4c,
                                     0x8f,
                                     0xcb,
                                     0x2c,
                                     0x50,
                                     0x7a,
                                     0xee,
                                     0x3e,
                                     0xd1,
                                     0x41,
                                     0x69,
                                     0xfa,
                                     0x0f)};

  auto smsText = decode(payload);
  std::println("{}", smsText);

  auto smsText2 = decode(payload2);
  std::println("{}", smsText2);

  static_assert("Simple SMS text." == decode(payload));
  static_assert(
    "Hello, C++! This is checking a std::string at "
    "compile time! Nice, isn't it?" ==
    decode(payload2));
}

I find that a valuable use case for a constexpr std::string which has a lot of benefits.

Andreas