Chapter 2
IN THIS CHAPTER
Considering the needs and uses for UDLs
Using the UDL features of the Standard Library
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.
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
.
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:
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.
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 |
---|---|---|---|
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
|
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;
}
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).
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
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.
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> >
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.
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:
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
double
float
long double
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.
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
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.
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
.
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
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.
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.