C++ provides developers with tools to add expressiveness to code that can make it easier for developers to understand. Two that I have found particularly useful are “type aliases”1 and “user defined literals”2 .
See the gist on GitHub
As an example, a developer might want to allocate some memory on the “stack”.
For argument’s sake, they might choose to use a
std::array
for this purpose.
// 256 Kibibytes
auto buffer = std::array<std::uint8_t, 262144>{};
So they might write something like
256 * 102
as the size parameter to better
convey their intention as, let’s face it, a number like 262144 might not be
useful to developers who aren’t familiar with their powers of two.
This is where user-defined literals come in. We can add the simplest of literals to improve it’s readability:
namespace c9::memory::size_literals {
constexpr auto operator "" _kib (long unsigned int value) -> long unsigned int {
return value * 1024;
}
} // namespace c9::memory::size_literals
We are using the IEC established suffix3 for byte quantities. Formally, kilobytes, megabytes, et. al. are defined in terms of base 10, whereas kibibytes, mibibytes, et al. are defined in terms of powers of 2. e.g a kilobyte is 1000 bytes, but a kibibyte is 1024 bytes; a megabyte is 1000000 bytes, but a mibibyte is 1048510 bytes.
Suddenly, the code becomes much more understandable, and clearly conveys that the size parameter is intended to convey an amount of memory:
using namespace c9::memory::size_literals;
auto buffer = std::array<std::uint8_t, 256_kib>{};
The
using namespace
declaration is required to expose the UDL and is implied for the rest of the article.
However, this has a bit of an issue. If the type parameter suddenly becomes
something larger than a byte, the whole thing doesn’t make any sense. We can
improve this: if we assume that our type parameter for an array representing
memory is going to be a std::uint8_t
4,
we can bind this to an array with a templated using-alias:
template <std::size_t Size>
using memory = std::array<std::uint8_t, Size>{};
Then our declaration of a block of memory becomes:
auto buffer = memory<256_kib>{};
From this, a developer can clearly tell that their colleague is declaring a buffer of memory with a size of 256 kibibytes. There are, of course, considerations for naming your type: perhaps you have established conventions already, or what i’ve used is too generic. In this specific case, I might advocate for something like this (adding in a namespace qualifier for even more context):
auto buffer = memory::buffer_of<256_kib>{};
For me, this raises some interesting question about common naming conventions, but I might have to think about it some more, and maybe do an article later. But, strictly speaking, I think this is better English.
¯\(ツ)/¯
You could definitely go wrong being too specific, e.g, calling it
stack_buffer
or similar because that’s “where” the automatic storage is. This wouldn’t be ideal because a developer could usenew stack_buffer<1234>{}
, in which case it would no longer be “stack” memory.
Now for some other use cases (at least for user-defined literals). Keep in mind that so far, with respect to UDLs, we don’t return any custom types, we’ve just used it to provide developers with context.
C++14 added literals for its std::complex
type, which represents complex numbers and the
std::chrono
library has literals for
various time periods. This I have found is one of the more useful ones. Compare:
#include <chrono>
using namespace std::chrono_literals;
auto duration = 2ms;
with:
#include <chrono>
auto duration = std::chrono::milliseconds(2);
Finally, within the realm if signal processing, you could add a UDL helper for representing frequency (or for that matter, delta time). Whether this returns a number so as to only add context, or returns a type like frequency, is up to the reader. e.g.:
auto freq = 440_Hz; // This is probably easier
auto delta = 0.00227_dt;
Footnotes
-
cppreference: Type alias, alias template ↩
-
cppreference: User-defined literals ↩
-
Wikipedia: Binary prefix ↩
-
Other relevant types could be
std::byte
,char
↩