Modern C++ In-Depth — Literal Operators and User-Defined Literals

Michael Kristofik
FactSet
Published in
4 min readMar 3, 2023

In this installment, we’re going to take a look at user-defined literals. First introduced in C++11, this feature allows developers to create custom suffixes that can be used to reduce verbosity.

Practical Motivation

Let’s suppose that we’re working on a library where we have to make frequent use of numeric values to represent buffer sizes. Such sizes are most commonly measured in either base-ten or base-two. While it’s relatively trivial to work with such values in base-ten, it can be a bit more challenging to do so with arbitrary base-two values.

For instance, the following represents a byte buffer that is exactly 22 mebibytes (i.e., the base-two equivalent of a megabyte) in size:

std::vector<std::byte> buffer(23'068'672);  // equivalent to 22 * 1024 * 1024

Note: the above example uses apostrophes as digit separators. This is a C++14 feature to aid readability of long literals. They are ignored by the compiler.

Without using a named variable or an inline comment, it may not be immediately obvious what this value means. By introducing a user-defined literal, we can significantly improve the clarity of the above code. To demonstrate this, let’s jump right in by defining two literals:

namespace file_size_literals
{
unsigned long long operator""_KiB(unsigned long long value)
{
return value * 1'024;
}

unsigned long long operator""_MiB(unsigned long long value)
{
return value * 1'024 * 1_KiB;
}
}

The first literal gives us an easy way to express sizes in kibibytes, while the second literal uses our kibibyte literal to define a mebibyte. Next, let’s use our new mebibyte literal to refactor the buffer declaration:

using namespace file_size_literals;
std::vector<std::byte> buffer(22_MiB);

To use the literal, we first have to pull in the namespace that contains the literal operator into the current scope. Then we simply append the literal as a suffix to the constructor argument.

Reminder: it is generally recommended that developers avoid writing using namespace at global scope in header files. Doing so may pollute the namespace, making it harder to disambiguate between types that share the same name.

Literal Rules

While user-defined literals are generally pretty flexible, there are a few limitations to be aware of. The range of parameter types that can be used to define a literal operator is somewhat restrictive. Here is the list of allowed parameters as of C++20:

// For demonstration purposes, we'll always use 'foo' as the suffix and
// we'll always return an int value.
int operator""_foo(const char*);
int operator""_foo(unsigned long long);
int operator""_foo(long double);
int operator""_foo(char);
int operator""_foo(wchar_t);
int operator""_foo(char8_t); // C++20 required
int operator""_foo(char16_t);
int operator""_foo(char32_t);
int operator""_foo(const char*, std::size_t);
int operator""_foo(const wchar_t*, std::size_t);
int operator""_foo(const char8_t*, std::size_t); // C++20 required
int operator""_foo(const char32_t*, std::size_t);
int operator""_foo(const char32_t*, std::size_t);
  • Literal operators that take a single character type as input are invoked when using a character literal as a prefix: 'a'_foo
  • Similarly, literal operators that take a pointer to a character type and a corresponding length are invoked when using a string literal as a prefix: "str"_foo
  • All user-defined suffixes must start with an underscore
  • Only those literals defined as part of the standard library are allowed to omit the leading underscore

While there are some restrictions on what can be passed to a literal operator (as demonstrated above), there are no such restrictions on what a literal operator may return, as long as it returns something.

Literal Operators in the Standard Library

The standard library defines a number of literal operators for various common and verbose types. A complete list is available at cppreference.com (scroll to the bottom of that page). Note that the standard library did not provide any literal operators in C++11, despite providing developers the tools to write their own.

Thanks to liberal use of inline namespaces, the standard library literal operators can be pulled into the current scope by optionally omitting the nested namespaces. In other words, std::literals::chrono_literals, std::chrono_literals, and std::literals all work to resolve references to the chrono operator literals. Similar variations exist for the other operator literals in the standard library.

As noted in the C++ Core Guidelines, the literals defined in the standard library are exempt from the general recommendation to avoid using namespace at global scope. Since the standard library literals are defined without a leading underscore, there should be no risk that any of these literals may collide with user-defined literals. However, we would still recommend limiting the use of any using namespace declarations at global scope. Instead, consider restricting such declarations to within your library’s namespace, if applicable.

Other Posts in This Series

The Modern C++ In-Depth series has explored some of the more technically challenging features of C++11 and beyond. Other topics we have covered previously:

Acknowledgments

Special thanks to all who contributed to this blog post:

Author: Tim Severeijns
Reviewers: Michael Kristofik and Jens Maurer

--

--

Michael Kristofik
FactSet
Editor for

Principal Software Architect at FactSet. I post on behalf of our company's C++ Guidance Group.