Creating cooked user-defined literals
Literals are constants of built-in types (numerical, Boolean, character, character string, and pointer) that cannot be altered in a program. The language defines a series of prefixes and suffixes to specify literals (and the prefix/suffix is actually part of the literal). C++11 allows us to create user-defined literals by defining functions called literal operators, which introduce suffixes for specifying literals. These work only with numerical character and character string types.
This opens the possibility of defining both standard literals in future versions and allows developers to create their own literals. In this recipe, we will learn how to create our own cooked literals.
Getting ready
User-defined literals can have two forms: raw and cooked. Raw literals are not processed by the compiler, whereas cooked literals are values processed by the compiler (examples can include handling escape sequences in a character string or identifying numerical values such as integer 2898 from literal 0xBAD). Raw literals are only available for integral and floating-point types, whereas cooked literals are also available for character and character string literals.
How to do it...
To create cooked user-defined literals, you should follow these steps:
- Define your literals in a separate namespace to avoid name clashes.
- Always prefix the user-defined suffix with an underscore (
_
). - Define a literal operator of one of the following forms for cooked literals:
T operator "" _suffix(unsigned long long int); T operator "" _suffix(long double); T operator "" _suffix(char); T operator "" _suffix(wchar_t); T operator "" _suffix(char16_t); T operator "" _suffix(char32_t); T operator "" _suffix(char const *, std::size_t); T operator "" _suffix(wchar_t const *, std::size_t); T operator "" _suffix(char16_t const *, std::size_t); T operator "" _suffix(char32_t const *, std::size_t);
The following example creates a user-defined literal for specifying kilobytes:
namespace compunits
{
constexpr size_t operator "" _KB(unsigned long long const size)
{
return static_cast<size_t>(size * 1024);
}
}
auto size{ 4_KB }; // size_t size = 4096;
using byte = unsigned char;
auto buffer = std::array<byte, 1_KB>{};
How it works...
When the compiler encounters a user-defined literal with a user-defined suffix, S
(it always has a leading underscore for third-party suffixes, as suffixes without a leading underscore are reserved for the standard library), it does an unqualified name lookup in order to identify a function with the name operator "" S
. If it finds one, then it calls it according to the type of the literal and the type of the literal operator. Otherwise, the compiler will yield an error.
In the example shown in the How to do it... section, the literal operator is called operator "" _KB
and has an argument of type unsigned long long int
. This is the only integral type possible for literal operators for handling integral types. Similarly, for floating-point user-defined literals, the parameter type must be long double
since for numeric types, the literal operators must be able to handle the largest possible values. This literal operator returns a constexpr
value so that it can be used where compile-time values are expected, such as specifying the size of an array, as shown in the preceding example.
When the compiler identifies a user-defined literal and has to call the appropriate user-defined literal operator, it will pick the overload from the overload set according to the following rules:
- For integral literals: It calls in the following order: the operator that takes an
unsigned long long
, the raw literal operator that takes aconst char*
, or the literal operator template. - For floating-point literals: It calls in the following order: the operator that takes a
long double
, the raw literal operator that takes aconst char*
, or the literal operator template. - For character literals: It calls the appropriate operator, depending on the character type (
char
,wchar_t
,char16_t
, andchar32_t
). - For string literals: It calls the appropriate operator, depending on the string type, that takes a pointer to the string of characters and the size.
In the following example, we're defining a system of units and quantities. We want to operate with kilograms, pieces, liters, and other types of units. This could be useful in a system that can process orders and you need to specify the amount and unit for each article.
The following are defined in the namespace units
:
- A scoped enumeration for the possible types of units (kilogram, meter, liter, and pieces):
enum class unit { kilogram, liter, meter, piece, };
- A class template to specify quantities of a particular unit (such as 3.5 kilograms or 42 pieces):
template <unit U> class quantity { const double amount; public: constexpr explicit quantity(double const a) : amount(a) {} explicit operator double() const { return amount; } };
- The
operator+
andoperator-
functions for thequantity
class template in order to be able to add and subtract quantities:template <unit U> constexpr quantity<U> operator+(quantity<U> const &q1, quantity<U> const &q2) { return quantity<U>(static_cast<double>(q1) + static_cast<double>(q2)); } template <unit U> constexpr quantity<U> operator-(quantity<U> const &q1, quantity<U> const &q2) { return quantity<U>(static_cast<double>(q1) – static_cast<double>(q2)); }
- Literal operators to create
quantity
literals, defined in an inner namespace calledunit_literals
. The purpose of this is to avoid possible name clashes with literals from other namespaces.If such collisions do happen, developers could select the ones that they should use using the appropriate namespace in the scope where the literals need to be defined:
namespace unit_literals { constexpr quantity<unit::kilogram> operator "" _kg( long double const amount) { return quantity<unit::kilogram> { static_cast<double>(amount) }; } constexpr quantity<unit::kilogram> operator "" _kg( unsigned long long const amount) { return quantity<unit::kilogram> { static_cast<double>(amount) }; } constexpr quantity<unit::liter> operator "" _l( long double const amount) { return quantity<unit::liter> { static_cast<double>(amount) }; } constexpr quantity<unit::meter> operator "" _m( long double const amount) { return quantity<unit::meter> { static_cast<double>(amount) }; } constexpr quantity<unit::piece> operator "" _pcs( unsigned long long const amount) { return quantity<unit::piece> { static_cast<double>(amount) }; } }
By looking carefully, you can note that the literal operators defined earlier are not the same:
_kg
is defined for both integral and floating-point literals; that enables us to create both integral and floating-point values such as1_kg
and1.0_kg
._l
and_m
are defined only for floating-point literals; this means we can only define quantity literals for these units with floating points, such as4.5_l
and10.0_m
._pcs
is only defined for integral literals; this means we can only define quantities of an integer number of pieces, such as42_pcs
.
Having these literal operators available, we can operate with various quantities. The following examples show both valid and invalid operations:
using namespace units;
using namespace unit_literals;
auto q1{ 1_kg }; // OK
auto q2{ 4.5_kg }; // OK
auto q3{ q1 + q2 }; // OK
auto q4{ q2 - q1 }; // OK
// error, cannot add meters and pieces
auto q5{ 1.0_m + 1_pcs };
// error, cannot have an integer number of liters
auto q6{ 1_l };
// error, can only have an integer number of pieces
auto q7{ 2.0_pcs}
q1
is a quantity of 1 kg; this is an integer value. Since an overloaded operator "" _kg(unsigned long long const)
exists, the literal can be correctly created from the integer 1. Similarly, q2
is a quantity of 4.5 kilograms; this is a real value. Since an overloaded operator "" _kg(long double)
exists, the literal can be created from the double floating-point value 4.5.
On the other hand, q6
is a quantity of 1 liter. Since there is no overloaded operator "" _l(unsigned long long)
, the literal cannot be created. It would require an overload that takes an unsigned long long
, but such an overload does not exist. Similarly, q7
is a quantity of 2.0 pieces, but piece literals can only be created from integer values and, therefore, this generates another compiler error.
There's more...
Though user-defined literals are available from C++11, standard literal operators have been available only from C++14. Further standard user-defined literals have been added to the next versions of the standard. The following is a list of these standard literal operators:
operator""s
for definingstd::basic_string
literals andoperator""sv
(in C++17) for definingstd::basic_string_view
literals:using namespace std::string_literals; auto s1{ "text"s }; // std::string auto s2{ L"text"s }; // std::wstring auto s3{ u"text"s }; // std::u16string auto s4{ U"text"s }; // std::u32string using namespace std::string_view_literals; auto s5{ "text"sv }; // std::string_view
operator""h
,operator""min
,operator""s
,operator""ms
,operator""us
, andoperator""ns
for creating anstd::chrono::duration
value:using namespace std::chrono_literals; // std::chrono::duration<long long> auto timer {2h + 42min + 15s};
operator""y
for creating anstd::chrono::year
literal andoperator""d
for creating anstd::chrono::day
literal that represents a day of a month, both added to C++20:using namespace std::chrono_literals; auto year { 2020y }; // std::chrono::year auto day { 15d }; // std::chrono::day
operator""if
,operator""i
, andoperator""il
for creating anstd::complex
value:using namespace std::complex_literals; auto c{ 12.0 + 4.5i }; // std::complex<double>
The standard user-defined literals are available in multiple namespaces. For instance, the ""s
and ""sv
literals for strings are defined in the namespace std::literals::string_literals
.
However, both literals
and string_literals
are inlined namespaces. Therefore, you can access the literals with using namespace std::literals
, using namespace std::string_literals
, or using namespace std::literals::string_literals
. In the previous examples, the second form was preferred.
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 raw user-defined literals to understand how to provide a custom interpretation of an input sequence so that it changes the normal behavior of the compiler
- Using inline namespaces for symbol versioning in Chapter 1, Learning Modern Core Language Features, to learn how to version your source code using inline namespaces and conditional compilation