User-Defined Literals
User-defined literals are a unique feature in all mainstream programming languages. They empower you to combine values with units.
The syntax
Literals are explicit values in a program. This can be a boolean like true, the number 3 or 4.15; but this can also be the character ‘a’ or the C string “hallo”. Even the lambda function [](int a, int b){ return a+b; } is a function literal. With C++11, it’s possible to generate user-defined literals by adding a suffix to a built-in literal for integers, floating points, characters, and C strings.
User-defined literals must obey the following syntax: built-in literal + _ + suffix.
Usually, you use the suffix for a unit:
101000101_b
63_s
10345.5_dm
123.45_km
100_m
131094_cm
33_cent
"Hallo"_i18n
But what is the critical benefit of user-defined literals? The C++ compiler maps the user-defined literals to the corresponding literal operator. This literal operator has – of course – to be implemented by the programmer.
The magic
Let’s look at the user-defined literal 0101001000_b that represents a binary value. The compiler maps the user-defined literal 0101001000_b to the literal operator operator”” _b(long long int bin). A few special rules are still missing.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
- There has to be a space between the quotation marks (“”) and the underscore with the suffix (_b).
- You have the binary value (0101001000) in the variable bin.
- The compilation will fail if the compiler doesn’t find the corresponding literal operator.
We get with C++14 an alternative syntax for user-defined types. They differ from the C++11 syntax because it requires no space. Therefore, it is possible to use reserved keywords like _C as a suffix and a user-defined literal of the form 11_C. The compiler will map 11_C to the literal operator””_C(unsigned long long int). The simple rule is now that you can use suffixes starting with an upper letter.
User-defined literals are the killer feature in modern C++ if you want to write safety-critical software. Why? Thanks to automatically mapping the user-defined literal to the literal operator, you can implement type-safe arithmetic. The compiler takes care that you don’t add apples and pears. Example?
How many meters do I drive on average per week? The question has occupied me for a long time.
Typesafe calculation with distances
Before I deal with the details, here is the main program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
// average.cpp #include <distance.h> #include <unit.h> using namespace Distance::Unit; int main(){ std:: cout << std::endl; std::cout << "1.0_km: " << 1.0_km << std::endl; std::cout << "1.0_m: " << 1.0_m << std::endl; std::cout << "1.0_dm: " << 1.0_dm << std::endl; std::cout << "1.0_cm: " << 1.0_cm << std::endl; std::cout << std::endl; std::cout << "0.001 * 1.0_km: " << 0.001 * 1.0_km << std::endl; std::cout << "10 * 1_dm: " << 10 * 1.0_dm << std::endl; std::cout << "100 * 1.0cm: " << 100 * 1.0_cm << std::endl; std::cout << "1_km / 1000: " << 1.0_km / 1000 << std::endl; std::cout << std::endl; std::cout << "1.0_km + 2.0_dm + 3.0_dm + 4.0_cm: " << 1.0_km + 2.0_dm + 3.0_dm + 4.0_cm << std::endl; std::cout << std::endl; auto work= 63.0_km; auto workPerDay= 2 * work; auto abbrevationToWork= 5400.0_m; auto workout= 2 * 1600.0_m; auto shopping= 2 * 1200.0_m; auto distPerWeek1= 4*workPerDay-3*abbrevationToWork+ workout+ shopping; auto distPerWeek2= 4*workPerDay-3*abbrevationToWork+ 2*workout; auto distPerWeek3= 4*workout + 2*shopping; auto distPerWeek4= 5*workout + shopping; std::cout << "distPerWeek1: " << distPerWeek1 << std::endl; auto averageDistance= getAverageDistance({distPerWeek1,distPerWeek2,distPerWeek3,distPerWeek4}); std::cout<< "averageDistance: " << averageDistance << std::endl; std::cout << std::endl; } |
The literal operators are implemented in the namespace Distance::unit. You should use namespaces for user-defined literals because name collisions are, for two reasons, very likely. First, the suffixes are usually very short; second, the suffixes usually stand for units that already established abbreviations. I used the suffixes km, m, dm, a, and cm in the program.
Here is the output of the program. My unit for distances is a meter.
I display in lines 12 – 15 the various distances; I calculate in lines 19 – 22 the meter in various resolutions. The last test looks quite promising.
1.0_km + 2.0_dm + 3.0_dm + 4.0_cm is 1000.54 m (line 54). The compiler takes care of the calculations with all units.
The key question remains. How many meters will I drive on average a week? For convenience, I define a few constants: work, workPerDay, abbrevationToWork, and shopping. These are my building blocks for the four weeks (lines 34 – 37). I went 493 km in the first week by car. The function getAverageDisttance (line 41) helps me to get the average. I have to invoke it with an initializer list. I drive 255900m on average per week. That needs to change! And that has changed. I’m now an independent trainer.
Under the hood
I ignored one fact. Where are the MyDistance objects defined? They are hidden in the program behind the automatic type deduction. Therefore, the explicit type for the variable work (line 28) is Distance::Distance. Line 28 is equivalent to Distance::MyDistance work= 63.0_km;
The following steps will automatically happen if I use 1.5_km + 105.1_m in the source code. The compiler maps at first the suffixes km and m to the corresponding literal operators; second, the compiler maps the + operator to the overloaded + operator of the MyDistance objects. Both steps can only work if the programmer implements the right operators in his contract. In this concrete case, he has to implement the literal operator and + operator. The black arrows in the graphic stand for the automatically performed mapping of the compiler. The red arrows stand for the functionality that the programmer has to implement.
What’s still missing to make the graphic complete? Right! The meat behind the red arrows.
Tasks of the programmer
At first, to the known overloading of operators. I overloaded for the class MyDistance basic arithmetic (lines 15 – 28) and the output operator (lines 30 – 33). The operators are global functions and can use – thanks to their friendship – the internals of the class. I store in the private variable m the distance. The function getAverageDistance (lines 41 – 45) is applying the overloaded addition and division operator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// distance.h #ifndef DISTANCE_H #define DISTANCE_H #include <iostream> #include <ostream> namespace Distance{ class MyDistance{ public: MyDistance(double i):m(i){} friend MyDistance operator+(const MyDistance& a, const MyDistance& b){ return MyDistance(a.m + b.m); } friend MyDistance operator-(const MyDistance& a,const MyDistance& b){ return MyDistance(a.m - b.m); } |
Shorter but more thrilling are the literal operators.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// unit.h #ifndef UNIT_H #define UNIT_H #include <distance.h> namespace Distance{ namespace Unit{ MyDistance operator "" _km(long double d){ return MyDistance(1000*d); } MyDistance operator "" _m(long double m){ return MyDistance(m); } MyDistance operator "" _dm(long double d){ return MyDistance(d/10); } MyDistance operator "" _cm(long double c){ return MyDistance(c/100); } } } #endif |
The literal operators take as an argument a long double and return a MyDistance object. MyDistance is automatically normalized to meters. And now? That was the whole functionality that the programmer has to provide.
I ignored one big optimization potential in my program. Almost all operations can be performed at compile time; almost all objects can be instantiated at compile time. To make that happen, I must declare the operations and objects as constexpr.
What’s next?
You can define user-defined literals not only for floating-point numbers. You can do it for integers, characters, and C strings. In addition, C++ has two ways to do integers and floating-point numbers. One is called cooked, the other raw. I have a lot more to write about user-defined literals. Wait for the next post.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!