Three new utility functions in C++23

Some time ago I wrote a blog post called Three C++23 features for common use. In this article, I want to continue on that idea and discuss three new utility functions that were added to C++23.

std::unreachable

This new function is available in the <utility> header. It’s intended to be used when you know you have an execution path in your code that cannot be reached but the compiler cannot figure that out. Informing the compiler could help to optimize impossible code branches.

A typical use case for this function are switch statements on a variable that can take only a limited set of values from its domain. For instance, an integer that can only be between 0 – 9. Here is a simple example with a switch that checks a char value and executes operations. Only a limited number of commands are supported but the argument is checked before invoking the function so it shouldn’t be possible to receive other values than already handled in the switch.

void execute_command(char ch)
{
   switch (ch)
   {
   case 'a':
      /* add */ break;
   case 'd':
      /* delete */ break;
   default:
      std::unreachable();
   }
}

bool is_valid(char ch)
{
   return ch == 'a' || ch == 'd';
}

int main()
{
   char ch = 0;
   while (!is_valid(ch))
      std::cin >> ch;
   execute_command(ch);
}

It could be argued though that it might be safer to handle the default case in some way to ensure safe failure rather than assume that a variable/argument would always take a limited number of values. This depends on the particularities of your code.

It should be noted that std::unreachable invokes undefined behavior.

Learn more

std::to_underlying

This is yet another utility function from the <utility> header. It converts an enumeration to its underlying type. It is basically syntactic sugar for the expression static_cast<std::underlying_type_t<Enum>>(e).

Let’s look at an example. Consider the following program:

void apply_style(int style)
{
   std::cout << std::format("applying style {}\n", style);
}

enum class styles
{
   A = 0x1,
   B = 0x2,
   C = 0x8000
};

int main()
{
   apply_style(static_cast<int>(styles::C));
}

You need a static_cast to convert from the scoped enum styles to int. This can be simplified using std::to_underlying, as follows:

int main()
{
   apply_style(std::to_underlying(styles::C));
}

The static_cast is actually moved to the body of this function, whose implementation can be as follows:

template <typename Enum>
constexpr auto to_underlying(Enum e) noexcept
{
    return static_cast<std::underlying_type_t<Enum>>(e);
}

Learn more

std::byteswap

This utility function is available in the <bit> header and reverses the bytes of an integral value. This is an addition to the bit utilities added with this header in C++20. Its purpose is to allow developers perform this byte reversal in a performant way without relying to compiler intrinsics.

Here is an example:

template <std::integral T>
void print_hex(T v)
{
   for (std::size_t i = 0; i < sizeof(T); ++i, v >>= 8)
   {
      std::cout << std::format("{:02X} ", static_cast<unsigned>(T(0xFF) & v));
   }
   std::cout << '\n';
}

int main()
{
   unsigned char a = 0xBA;
   print_hex(a);                    // BA
   print_hex(std::byteswap(a));     // BA

   unsigned short b = 0xBAAD;
   print_hex(b);                    // AD BA
   print_hex(std::byteswap(b));     // BA AD

   int c = 0xBAADF00D;
   print_hex(c);                    // 0D F0 AD BA
   print_hex(std::byteswap(c));     // BA AD F0 0D

   long long d = 0xBAADF00DBAADC0FE;
   print_hex(d);                    // FE C0 AD BA 0D F0 AD BA
   print_hex(std::byteswap(d));     // BA AD F0 0D BA AD C0 FE
}

Byte swapping is important when transferring data between system that use different order for the sequence of bytes stores in memory. This is called endianness. In Big Endian systems, the most significant byte is stored at the smaller address (first). In Little Endian systems, the least significant byte is stored at the smaller address. x86/x64 architectures use the little endian format. ARM supports both little and big endianness. Network protocols specify big endian for the order of transmission which requires swapping to/from systems using little endian order.

Learn more


All these utility functions are supported by the major compilers. You can check the compiler support matrix here.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.