Chapter 2

Working with User-Defined Literals (UDLs)

IN THIS CHAPTER

check Considering the needs and uses for UDLs

check Using the UDL features of the Standard Library

check Developing custom UDLs

Previous chapters have discussed literals as a kind of constant. For example, in the expression X = 5, the number 5 is a literal constant. The constant X stands in for the value 5 in application code. Using a literal enables you to create code that states the use of a value clearly, rather than having code that is filled with mystery values that no one can figure out. In addition, using literals lets you change constant values in one place, rather than in each place they’re needed in an application.

Up to this point, you have used every other kind of literal constant in the various examples except for User-Defined Literals (UDLs). Unlike other kinds of literal constants, a UDL isn’t defined as part of the C++ compiler — you create UDLs as needed to make your code more readable and easier to manage. In some cases, UDLs come with the libraries you use in C++, such as the Standard Library. This chapter does discuss UDLs that come as part of the Standard Library, but it also looks at how you’d create your own UDLs as needed.

Remember UDLs aren’t part of older C++ specifications. In fact, they first made an appearance in C++ 11. This means that you must configure Code::Blocks to use the features provided by C++ 11 by using the technique found in the “Working with ranges” section of Book 1, Chapter 5. If you don’t perform the configuration for each example in this chapter, you see error messages telling you that the default setup doesn’t provide the desired support.

There are also some small, but important, changes for UDLs in C++ 20 that aren’t covered in this chapter because they’re used at a more detailed level. You can read about these tweaks to UDLs at https://en.cppreference.com/w/cpp/language/user_literal.

Remember You don’t have to type the source code for this chapter manually. In fact, using the downloadable source is a lot easier. You can find the source for this chapter in the \CPP_AIO4\BookVII\Chapter02 folder of the downloadable source. See the Introduction for details on how to find these source files.

Understanding the Need for UDLs

The whole point of literals is to make code more readable and easier to maintain. However, built-in literals are limited to a few data types, summarized as follows:

  • Integer
  • Floating-point
  • Character
  • String
  • Boolean
  • Pointer
  • UDL

Sometimes you need a literal of a type other than these built-in types, and that’s where UDLs come into play. Unlike variables, the value of a UDL is always known at compile time. The compiler substitutes whatever value you define for the UDL with the actual value in the code. The purpose of the UDL is to make the code easier for the human developer to read and understand. After that task is completed, the compiler is free to use the actual value referenced by the UDL in the compiled code so that the application doesn’t need to convert it during runtime. Your application therefore runs faster and uses fewer resources while remaining easy to read.

Remember Built-in literals are straightforward because they’re based on core types. A UDL can be as complex as you need it to be to express a real-world data type. For example, if you’re involved in a field that uses imaginary numbers, you can create a UDL to fulfill that need. You can also perform data conversions and other tasks that would be time consuming to perform in other ways. You can even create side effects, such as performing some sort of output, using a UDL.

Prefixes and suffixes

Saving time and effort is part of the reason you use literals. There is a shorthand way to create literals and ensure that you obtain the correct constant type. Many of the standard literals provide you with a prefix or suffix that you can use to tell the compiler how to interpret them. Precisely how the prefix or suffix is interpreted depends on how you use it. For example, a suffix of U could mean an unsigned int when used with an int value, while a prefix of U could mean a char32_t const pointer when used with a character string. Table 2-1 shows a listing of the prefixes and suffixes that most compilers support.

TABLE 2-1 Standard Prefixes and Suffixes

Data Type

Prefix

Suffix

Resultant Type

int

U or u

unsigned int

int

L or l

long

int

UL, Ul, uL, ul, LU, Lu, lU, or lu

unsigned long

int

LL or ll

long long

int

ULL, Ull, uLL, ull, LLU, LLu, llU, or llu

unsigned long long

double

F or f

float

double

L or l

long double

char

L

wchar_t

char

U

char32_t

char

U

char16_t

String

L

wchar_t const*

String

U

char32_t const*

String

U

char16_t const*

Using the prefixes and suffixes can save you considerable time. The PrefixesAndSuffixes example in Listing 2-1 demonstrates how you’d employ them to create variables of various sorts.

LISTING 2-1: Creating Literals Using Prefixes and Suffixes

#include <iostream>
#include <typeinfo>
#include <cxxabi.h>

using namespace std;
using namespace abi;

char* Demangle(const char* Object) {
int Status;
char* RealName;
RealName = __cxa_demangle(Object, 0, 0, &Status);
return RealName;
}

int main() {
auto Int1 = 23;
auto Int2 = 23L;
auto Int3 = 23U;
auto Int4 = 23u;

auto String1 = "Hello";
auto String2 = L"Hello";
auto String3 = U"Hello";
auto String4 = u"Hello";

cout << Int1 << endl
<< Demangle(typeid(Int1).name()) << endl;
cout << Int2 << endl
<< Demangle(typeid(Int2).name()) << endl;
cout << Int3 << endl
<< Demangle(typeid(Int3).name()) << endl;
cout << Int4 << endl
<< Demangle(typeid(Int4).name()) << endl;

cout << String1 << endl
<< Demangle(typeid(String1).name()) << endl;
cout << String2 << endl
<< Demangle(typeid(String2).name()) << endl;
cout << String3 << endl
<< Demangle(typeid(String3).name()) << endl;
cout << String4 << endl
<< Demangle(typeid(String4).name()) << endl;
return 0;
}

Tip The Demangle() function is GCC specific. Most C++ compilers mangle (modify the spelling of) keywords and type information to make an application harder for someone to reverse-assemble (convert from machine language back into C++ source code). To determine type information, you use the typeid() function to create a typeinfo structure. The name() function returns the type name found in this structure to display it onscreen. However, this name is mangled, so you must use the Demangle() function to change it back to its original readable form.

Most of the examples in this chapter rely on the auto keyword to automatically detect the variable type created by a UDL. This keyword is an important feature for newer C++ applications that make use of the new extensions that the language provides. You can read about the auto keyword in the “Using the auto keyword with lambda expressions” section of Book 3, Chapter 2. In this case, the code uses the auto keyword to detect the output of the literal prefix or suffix so that the variable is automatically the correct type for a situation. When you run this application, you see the following output:

23
int
23
long
23
unsigned int
23
unsigned int
Hello
char const*
0x46e02c
wchar_t const*
0x46e038
char32_t const*
0x46e02c
char16_t const*

Even though the data is the same in every case, the variables used to hold the data differ because of the prefix or suffix used to create the variable. Notice that the same prefix or suffix has different effects depending on the type of the variable to which it’s applied. In addition, sometimes the case of the prefix or suffix matters (as in working with a string).

Differentiating between raw and cooked

There are many ways to define literals. Of course, the kind of information that a literal affects is the most common method. However, literals can also be raw or cooked. A raw literal receives input from the application source and doesn’t interpret it in any way. This means that the information is interpreted character by character, precisely as the sender has presented it. Cooked literals interpret the sender’s input and automatically perform any required conversions to make the data usable to the recipient.

The easiest way to see this principle in action is through an example. The RawAndCooked example, shown in Listing 2-2, demonstrates the technique used to create either raw or cooked string processing.

LISTING 2-2: Using Raw and Cooked String Processing

#include <iostream>

using namespace std;

int main() {
auto Cooked = "(Hello\r\nThere)";
auto Raw = R"(Hello\r\nThere)";
cout << Cooked << endl;
cout << Raw << endl;
}

Most of the time when you see the \r\n combination, you know that the application will output a carriage return and linefeed combination. This is the cooked method of processing a string. The string is interpreted and any escape characters converted into control characters (characters that are normally regarded as commands, rather than data, such as the carriage return). However, notice how the Raw string is created. The R in front of the string tells the compiler to create the variable without interpreting the content. Here’s the output you see from this example:

(Hello
There)
Hello\r\nThere

Remember Notice that the cooked form does output the parentheses, but the raw form doesn’t. The parentheses are required as part of the raw form input. As you might imagine, the cooked form outputs the \r\n combination as control characters, while the raw form outputs the actual characters.

Working with the UDLs in the Standard Library

Even though you can currently create UDLs for the basic types described in the “Understanding the Need for UDLs” section, earlier in this chapter, there are many situations in which developers need UDLs for classes as well. In some cases, these classes are part of the Standard Library. Rather than have a number of nonstandard implementations of these UDLs, the standards committee decided to add the UDLs directly to the Standard Library. You can read the details in the “User-defined Literals for Standard Library Types” at http://www.open-std.org/jtc1/sc33/wg21/docs/papers/2013/n3531.pdf. Consistent and standardized UDLs are now attached to some classes. The following sections describe the more important classes and show how to use them.

std::basic_string

The std::basic_string class enables you to work with sequences of char-like objects. The class currently has templates defined for

  • char
  • wchar_t
  • char16_t
  • char32_t

However, the class could easily be extended for other kinds of characters. In addition, the templates let you specify character traits and the method used to store the data in memory. The essential idea behind the basic_string is to enable you to accommodate a variety of character types within one character class to simplify coding.

In C++ 14, the Standard Library includes built-in literal support for basic_string. All you need to do is add the s suffix to a string to create one. However, it’s important to get an idea of how all this works behind the scenes. The BasicString example, shown in Listing 2-3, demonstrates three techniques for creating a basic_string object.

LISTING 2-3: Three Techniques for Creating a basic_string

#include <iostream>
#include <typeinfo>
#include <cxxabi.h>

using namespace std;
using namespace abi;

string operator"" _s(const char * str, unsigned len) {
return string{str, len};
}

char* Demangle(const char* Object) {
int Status;
char* RealName;
RealName = __cxa_demangle(Object, 0, 0, &Status);
return RealName;
}

int main() {
basic_string<char> StdString = "A standard string.";
auto AutoString = "This is an auto string."_s;
auto UDLString = "This is a UDL string."s;

cout << StdString << endl <<
Demangle(typeid(StdString).name()) << endl;
cout << AutoString << endl <<
Demangle(typeid(AutoString).name()) << endl;
cout << UDLString << endl <<
Demangle(typeid(UDLString).name()) << endl;
return 0;
}

This example performs three essential levels of conversion so that you can see the progression from one to another. In the first case, you see the straightforward method for creating a simple basic_string object, StdString. As you can see, it works just like any other template. The second case relies on a C++ 11 type operator definition to emulate the UDL that is included as part of C++ 14. The “Creating Your Own UDLs” section of this chapter tells you all the details about creating such an operator. All you really need to know for now is that the operator makes it possible to use a shortcut when creating basic_string objects. The third case shows the C++14 version of the same _s definition, but this one is built right into the Standard Library so you don’t have to do anything special to use it. In all three cases, you create the same basic_string object type, but the technique differs each time. When you run this example, you see the following output:

A standard string.
std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >
This is an auto string.
std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >
This is a UDL string.
std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >

Technical stuff There seems to be some confusion online as to how the raw and cooked versions of basic_string should work. In looking at the _s and s operators, the code already provides both raw and cooked implementations. For example, if you used this code with the _s operator:

auto RawString = R"(This is a\r\nraw string.)";
auto CookedString = "This is a\r\ncooked string.";
cout << RawString << endl;
cout << CookedString << endl;

you’d see the following output:

This is a\r\nraw string.
This is a
cooked string.

The s operator works in the same manner. So, you can use either raw or cooked strings with the same operator and receive the appropriate results.

std::complex

A complex number consists of a real number and an imaginary number that are paired together. (Just in case you’ve completely forgotten about complex numbers, you can read about them at http://www.mathsisfun.com/numbers/complex-numbers.html.) Real-world uses for complex numbers include:

  • Electrical engineering
  • Fluid dynamics
  • Quantum mechanics
  • Computer graphics
  • Dynamic systems

There are other uses for complex numbers, too, but this list should give you some ideas. In general, if you aren’t involved in any of these disciplines, you probably won’t ever encounter complex numbers. However, the Standard Library provides full support for complex numbers, just in case you do need them.

As with the BasicString example, this example shows the progression from a standard declaration to the C++ 14 suffix. The ComplexNumber example, shown in Listing 2-4, demonstrates all three stages so that you can see how both the C++ 14 suffix and the C++ 11 UDL forms work.

LISTING 2-4: Three Techniques for Creating a complex Number

#include <iostream>
#include <complex>

using namespace std;

complex<long double> operator"" _i(long double Value) {
return complex<double>(0, Value);
}
int main() {
complex<double> StdComplex(0, 3.14);
auto AutoComplex = 3.14_i;
auto UDLComplex = 3.14i;
auto NonZeroRealPart = 2.01 + 3.14i;

cout << StdComplex.real() << "\t"
<< StdComplex.imag() << endl;
cout << AutoComplex.real() << "\t"
<< AutoComplex.imag() << endl;
cout << UDLComplex.real() << "\t"
<< UDLComplex.imag() << endl;
cout << NonZeroRealPart.real() << "\t"
<< NonZeroRealPart.imag() << endl;
return 0;
}

The example declares variables of all three types and assigns values to them. It also creates a version of a variable with a non-zero real part so you can see how to perform this task. You provide the real part plus the imaginary part as two values. It then displays both the real and imaginary parts of the number. When you run this example, you see the following output:

0 3.14
0 3.14
0 3.14
2.01 3.14

Remember You can create three kinds of complex numbers. The following list shows the suffixes used for each type:

  • i: double
  • if: float
  • il: long double

Warning The auto UDLComplex = 3.14i; form of declaration generates an error when you use the -fext-numeric-literals switch with the GNU GCC Compiler. You see this switch demonstrated in the “Specifying a precision” section of Book 6 Chapter 2. When performing tasks such as using hexadecimal notation with an exponent, you need to use one of the other complex number declaration types instead.

std::chrono::duration

The chrono::duration class serves to mark the passage of time. It answers the question of how much time has elapsed between two events. Developers use it for all sorts of time-related purposes.

Remember A chrono::duration object relies on a second as the standard duration between ticks. A tick is a single time duration interval. Using the standard setup, each tick equals one second. However, you can use the ratio object to define a new tick duration. For example, if you define ratio<60>, each tick lasts one minute. Likewise, defining ratio<1, 5> sets each tick to last one fifth of a second.

You can also change one interval to another using duration_cast with either a standard interval, such as chrono::seconds, or any interval typedef that you want to create. For example, typedef chrono::duration<double, ratio<1, 5>> fifths; defines an interval called fifths.

There is a lot more to talk about with the chrono::duration class, but you now have enough information to work with the Duration example, shown in Listing 2-5. As with previous examples, this one shows a progression from defining a variable directly, to using a custom UDL, and finally the built-in support that C++ 14 provides.

LISTING 2-5: Three Techniques for Creating a chrono::duration

#include <iostream>
#include <chrono>

using namespace std;

chrono::duration<unsigned long long> operator"" _m(
unsigned long long Value) {
return chrono::duration<int, ratio<60>>(Value);
}

int main() {
chrono::duration<int, ratio<60>>StdTime(20);
auto AutoTime(20_m);
auto UDLTime(20min);

cout << chrono::duration_cast<chrono::seconds>(StdTime)
.count() << endl;
cout << chrono::duration_cast<chrono::seconds>(AutoTime)
.count() << endl;
cout << chrono::duration_cast<chrono::seconds>(UDLTime)
.count() << endl;
return 0;
}

The example demonstrates a few features of the chrono::duration class. However, it focuses again on the progression from defining the variable by hand to using a shortcut to perform the task. Notice that the UDL relies on an integer value in this case, rather than a floating-point type. The value of 20 minutes is converted to seconds for output. As a result, you see these values when you run the application:

1200
1200
1200

Remember The Standard Library supports a number of suffixes for chrono::duration when you use C++ 14. The following list shows the individual suffixes and tells you what they mean:

  • h: Hours
  • min: Minutes
  • s: Seconds
  • ms: Milliseconds
  • us: Microseconds
  • ns: Nanoseconds

Creating Your Own UDLs

The Standard Library, coupled with the built-in features of C++, provide you with an interesting array of literals. However, the true value of literals becomes more obvious when you create your own. There are many different needs you can address using UDLs, but three common needs are supporting data conversions, making custom types easier to work with, and obtaining desired side effects without the usual number of coding problems.

Remember Although built-in or Standard Library literals come in both prefix and suffix form, you can create only the suffix form when defining your own literals. In addition, the suffix must begin with an underscore. The underscore serves to help prevent conflicts with existing suffixes and to ensure that other developers know that the literal is a custom (nonstandard) form.

Developing a conversion UDL

You can encapsulate conversions within a UDL. All you need to do after you create such a UDL is provide the appropriate suffix when defining the constant to obtain the result you want. The CustomUDL01 example, in Listing 2-6, demonstrates a technique for defining a conversion that changes the radius input to the area of a circle in the constant.

LISTING 2-6: Defining a Data Conversion UDL

#include <iostream>

using namespace std;

constexpr long double operator""
_circ(long double radius) {
return radius*radius*3.141592;
}

int main() {
double x = 5.0_circ;
cout << "The circle's area is: " << x << endl;
return 0;
}

To create the UDL, the example relies on a constexpr with a return value of a long double and an input value, radius, of a long double. The equation for computing the area of a circle is πr2. As you can see, the example performs the correct computation as part of the constexpr.

Remember Whenever you create a custom UDL, the compiler forces you to use the largest type for the conversion. What this means is that you must use a long double for floating-point literals and unsigned long long for integer literals. Even if you later choose to use a smaller type, as is done in this example by declaring x as a double, the literal itself must employ the largest possible type.

To declare a UDL of the new type, the example creates x, which uses the _circ suffix. It then outputs the result onscreen. When you run this example, you see that the correct value has been placed in x, as shown here:

The circle's area is: 78.5398

Developing a custom type UDL

A lot of the code you encounter relies on custom types that are hard to follow and understand. Creating a UDL to simplify the code makes things clearer and reduces the potential for error. The CustomUDL02 example, shown in Listing 2-7, shows a custom type, the operator used to create the UDL, and how the UDL is used to define a literal.

LISTING 2-7: Creating a UDL for a Custom Type

#include <iostream>

using namespace std;

struct MyType {
MyType (double Input):Value(Input){}
double Value;
};

MyType operator"" _mytype (long double Value) {
return MyType(Value);
}

int main() {
auto UDLType = 145.6_mytype;
cout << UDLType.Value << endl;
return 0;
}

For this technique to work, you must create a constructor for your type that accepts the number of inputs required to configure the type. At minimum, the constructor must accept one type or the input value the user provides is lost. The custom type need not support the same size data type as required by the operator, but they must be of the same sort. For example, you couldn’t transition a long double to an int.

When you run this example, you see an output value of 145.6, which is the value you input to the custom type. You can handle fairly complex setups using this approach. The user of your custom type obtains the capability to create clear code that’s easy to follow and interpret, even when the underlying types are complex.

Using a custom UDL for side effects

One of the most interesting uses for UDLs is to create side effects (an operation other than the usual or normal operation, either to make the application shorter and more efficient or to provide added flexibility). You want to define a certain kind of operation that takes place as a result of defining the literal. What you get is still a literal, but a literal that doesn’t necessarily denote a value that you plan to use later. The CustomUDL03 example, shown in Listing 2-8, shows one such nontraditional use.

LISTING 2-8: Using UDLs to Create an Interesting Side Effect

#include <iostream>

using namespace std;

void operator"" _countdown (unsigned long long Value) {
for (int i = Value; i >= 0; i--)
cout << i << endl;
}

int main() {
5_countdown;
return 0;
}

Notice that the _countdown operator isn’t attached to something that you’d normally associate with a value. In fact, it doesn’t return a value at all. What you get instead is a side effect. When you run this example, you see this output.

5
4
3
2
1
0

What has happened is that the compiler has replaced 5_countdown with individual cout statements, one for each iteration of the loop. You end up with six cout statements that output the values between 5 and 0 (in reverse order). The side effect UDL opens all sorts of interesting possibilities for creating code that simplifies certain repetitive tasks in a manner that makes their use obvious.