Creating raw user-defined literals
In the previous recipe, we looked at the way C++11 allows library implementers and developers to create user-defined literals and the user-defined literals available in the C++14 standard. However, user-defined literals have two forms: a cooked form, where the literal value is processed by the compiler before being supplied to the literal operator, and a raw form, in which the literal is not processed by the compiler before being supplied to the literal operator. The latter is only available for integral and floating-point types. Raw literals are useful for altering the compiler's normal behavior. For instance, a sequence such as 3.1415926 is interpreted by the compiler as a floating-point value, but with the use of a raw user-defined literal, it could be interpreted as a user-defined decimal value. In this recipe, we will look at creating raw user-defined literals.
Getting ready
Before continuing with this recipe, it is strongly recommended that you go through the previous one, Creating cooked user-defined literals, as general details about user-defined literals will not be reiterated here.
To exemplify the way raw user-defined literals can be created, we will define binary literals. These binary literals can be of 8-bit, 16-bit, and 32-bit (unsigned) types. These types will be called byte8
, byte16
, and byte32
, and the literals we will create will be called _b8
, _b16
, and _b32
.
How to do it...
To create raw user-defined literals, you should follow these steps:
- Define your literals in a separate namespace to avoid name clashes.
- Always prefix the used-defined suffix with an underscore (
_
). - Define a literal operator or literal operator template of the following form:
T operator "" _suffix(const char*); template<char...> T operator "" _suffix();
The following example shows a possible implementation of 8-bit, 16-bit, and 32-bit binary literals:
namespace binary
{
using byte8 = unsigned char;
using byte16 = unsigned short;
using byte32 = unsigned int;
namespace binary_literals
{
namespace binary_literals_internals
{
template <typename CharT, char... bits>
struct binary_struct;
template <typename CharT, char... bits>
struct binary_struct<CharT, '0', bits...>
{
static constexpr CharT value{
binary_struct<CharT, bits...>::value };
};
template <typename CharT, char... bits>
struct binary_struct<CharT, '1', bits...>
{
static constexpr CharT value{
static_cast<CharT>(1 << sizeof...(bits)) |
binary_struct<CharT, bits...>::value };
};
template <typename CharT>
struct binary_struct<CharT>
{
static constexpr CharT value{ 0 };
};
}
template<char... bits>
constexpr byte8 operator""_b8()
{
static_assert(
sizeof...(bits) <= 8,
"binary literal b8 must be up to 8 digits long");
return binary_literals_internals::
binary_struct<byte8, bits...>::value;
}
template<char... bits>
constexpr byte16 operator""_b16()
{
static_assert(
sizeof...(bits) <= 16,
"binary literal b16 must be up to 16 digits long");
return binary_literals_internals::
binary_struct<byte16, bits...>::value;
}
template<char... bits>
constexpr byte32 operator""_b32()
{
static_assert(
sizeof...(bits) <= 32,
"binary literal b32 must be up to 32 digits long");
return binary_literals_internals::
binary_struct<byte32, bits...>::value;
}
}
}
How it works...
First of all, we define everything inside a namespace called binary
and start with introducing several type aliases: byte8
, byte16
, and byte32
. These represent integral types of 8 bits, 16Â bits, and 32 bits, as the names imply.
The implementation in the previous section enables us to define binary literals of the form 1010_b8
(a byte8
value of decimal 10) or 000010101100_b16
(a byte16
value of decimal 2130496). However, we want to make sure that we do not exceed the number of digits for each type. In other words, values such as 111100001_b8
should be illegal and the compiler should yield an error.
The literal operator templates are defined in a nested namespace called binary_literal_internals
. This is a good practice in order to avoid name collisions with other literal operators from other namespaces. Should something like that happen, you can choose to use the appropriate namespace in the right scope (such as one namespace in a function or block and another namespace in another function or block).
The three literal operator templates are very similar. The only things that are different are their names (_b8
, _16
, and _b32
), return type (byte8
, byte16
, and byte32
), and the condition in the static assert that checks the number of digits.
We will explore the details of variadic templates and template recursion in a later recipe; however, for a better understanding, this is how this particular implementation works: bits
is a template parameter pack that is not a single value, but all the values the template could be instantiated with. For example, if we consider the literal 1010_b8
, then the literal operator template would be instantiated as operator"" _b8<'1', '0', '1', '0'>()
. Before proceeding with computing the binary value, we check the number of digits in the literal. For _b8
, this must not exceed eight (including any trailing zeros). Similarly, it should be up to 16Â digits for _b16
and 32 for _b32
. For this, we use the sizeof...
operator, which returns the number of elements in a parameter pack (in this case, bits
).
If the number of digits is correct, we can proceed to expand the parameter pack and recursively compute the decimal value represented by the binary literal. This is done with the help of an additional class template and its specializations. These templates are defined in yet another nested namespace, called binary_literals_internals
. This is also a good practice because it hides (without proper qualification) the implementation details from the client (unless an explicit using namespace
directive makes them available to the current namespace).
Even though this looks like recursion, it is not a true runtime recursion. This is because after the compiler expands and generates the code from templates, what we end up with is basically calls to overloaded functions with a different number of parameters. This is explained later in the Writing a function template with a variable number of arguments recipe.
The binary_struct
class template has a template type of CharT
for the return type of the function (we need this because our literal operator templates should return either byte8
, byte16
, or byte32
) and a parameter pack:
template <typename CharT, char... bits>
struct binary_struct;
Several specializations of this class template are available with parameter pack decomposition. When the first digit of the pack is '0'
, the computed value remains the same, and we continue expanding the rest of the pack. If the first digit of the pack is '1'
, then the new value is 1, shifted to the left with the number of digits in the remainder of the pack bit, or the value of the rest of the pack:
template <typename CharT, char... bits>
struct binary_struct<CharT, '0', bits...>
{
static constexpr CharT value{
binary_struct<CharT, bits...>::value };
};
template <typename CharT, char... bits>
struct binary_struct<CharT, '1', bits...>
{
static constexpr CharT value{
static_cast<CharT>(1 << sizeof...(bits)) |
binary_struct<CharT, bits...>::value };
};
The last specialization covers the case where the pack is empty; in this case, we return 0:
template <typename CharT>
struct binary_struct<CharT>
{
static constexpr CharT value{ 0 };
};
After defining these helper classes, we could implement the byte8
, byte16
, and byte32
binary literals as intended. Note that we need to bring the content of the namespace binary_literals
into the current namespace in order to use the literal operator templates:
using namespace binary;
using namespace binary_literals;
auto b1 = 1010_b8;
auto b2 = 101010101010_b16;
auto b3 = 101010101010101010101010_b32;
The following definitions trigger compiler errors:
// binary literal b8 must be up to 8 digits long
auto b4 = 0011111111_b8;
// binary literal b16 must be up to 16 digits long
auto b5 = 001111111111111111_b16;
// binary literal b32 must be up to 32 digits long
auto b6 = 0011111111111111111111111111111111_b32;
The reason for this is that the condition in static_assert
is not met. The length of the sequence of characters preceding the literal operator is greater than expected, in all cases.
See also
- Using raw string literals to avoid escaping characters to learn how to define string literals without the need to escape special characters
- Creating cooked user-defined literals to learn how to create literals of user-defined types
- Writing a function template with a variable number of arguments in Chapter 3 to see how variadic templates enable us to write functions that can take any number of arguments
- Creating type aliases and alias templates in Chapter 1 to learn about aliases for types