User-Defined Literals in C++: Complete Tutorial
User-Defined Literals in C++: A Comprehensive Tutorial
User-Defined Literals (UDLs) are a powerful C++ feature that allows programmers to define custom suffixes for literals, enabling more readable and expressive code. By attaching units or semantic meaning directly to values, UDLs help prevent unit conversion errors, improve code clarity, and shift computations to compile time. This tutorial explores UDLs in depth, covering their syntax, restrictions, practical applications, and common pitfalls.
Note: While this tutorial focuses on C++, it is published on CoreTechED.com as a continuation of our C programming series to clarify the distinction between C literals and C++ user-defined literals. C itself does not support user-defined literals; this is a C++-only feature.
Conceptual Introduction & Fundamentals
What are User-Defined Literals?
A literal is a fixed value written directly in source code, such as 42, 3.14, 'A', or "hello". Built-in literals in C++ have predefined types:
cpp
42 // int 2.4 // double 3.2F // float 'w' // char 32ULL // unsigned long long 0xD0 // hexadecimal int "cd" // const char[3]
User-Defined Literals extend this concept by allowing you to define custom suffixes that can be appended to literals. These suffixes invoke a special function called a literal operator, which transforms the literal into a user-defined type or performs custom computations.
Why Use UDLs?
Consider the problem of representing physical quantities:
cpp
long double weight = 2.3; // Is this pounds? kilograms? grams?
Without UDLs, the unit is ambiguous, leading to potential errors. With UDLs, you can attach units directly:
cpp
weight = 2.3_kg; // clearly kilograms ratio = 2.3_kg / 1.2_lb; // different units handled correctly
Advantages of UDLs:
- Readability: Code becomes self-documenting (
3.5_m,10_km,5_s). - Type safety: Units are encoded in the literal, reducing conversion errors.
- Compile-time computation: UDLs with
constexprallow calculations at compile time, improving runtime performance. - Expressiveness: Custom types integrate naturally with literal syntax.
How UDLs Work
A UDL is treated as a call to a literal operator. The syntax for defining a literal operator is:
cpp
return_type operator"" _suffix(parameters);
Only the suffix form is supported; there is no prefix form for user-defined literals. The suffix must begin with an underscore (_) to avoid conflicts with future standard library suffixes (which do not use underscores).
Example:
cpp
long double operator"" _kg(long double x) { return x * 1000; // convert kilograms to grams } Usage:
cpp
long double weight = 3.6_kg; // calls operator"" _kg(3.6)
The compiler substitutes the literal with the result of the literal operator at compile time (if constexpr).
UDLs vs. Function Calls
UDLs are essentially syntactic sugar for function calls. Instead of writing:
cpp
long double weight = kilograms(3.6);
You write:
cpp
long double weight = 3.6_kg;
This makes the code more natural and readable, especially when dealing with physical units, strings, or custom numeric types.
Syntax & Core Mechanics
Literal Operator Syntax
The general syntax for a literal operator is:
cpp
return_type operator"" _suffix(parameter_list);
The suffix must start with an underscore (_). Suffixes without an underscore are reserved for the C++ standard library.
Allowed Parameter Types
The C++ standard restricts the parameter types for literal operators to the following:
| Parameter Type | Literal Category |
|---|---|
unsigned long long | Integer literals |
long double | Floating-point literals |
char | Character literals |
wchar_t | Wide character literals |
char16_t | UTF-16 character literals |
char32_t | UTF-32 character literals |
const char* | String literals |
const char*, std::size_t | String literals with length |
const wchar_t*, std::size_t | Wide string literals |
const char16_t*, std::size_t | UTF-16 string literals |
const char32_t*, std::size_t | UTF-32 string literals |
The return type can be any type, including user-defined types.
Categories of UDLs
1. Integer Literals
For integer literals, the parameter must be unsigned long long. If the literal is too large for unsigned long long, the program is ill-formed.
cpp
// Integer UDL: convert to a custom BigInt or other type MyInt operator"" _myint(unsigned long long x) { return MyInt(x); } // Usage auto a = 123_myint; 2. Floating-Point Literals
For floating-point literals, the parameter must be long double.
cpp
// Floating-point UDL: kilometers to meters constexpr long double operator"" _km(long double x) { return x * 1000.0L; } // Usage auto distance = 5.2_km; // 5200.0 3. Character Literals
For character literals, the parameter is char.
cpp
// Character UDL: convert to uppercase (not constexpr in C++11) char operator"" _upper(char c) { return (c >= 'a' && c <= 'z') ? c - 'a' + 'A' : c; } // Usage char c = 'a'_upper; // 'A' 4. String Literals
For string literals, the parameter can be const char* (without length) or const char*, std::size_t (with length). The length version is preferred because it can handle embedded null characters.
cpp
#include <string> #include <cstddef> // String UDL: create std::string from const char* std::string operator"" _s(const char* str, std::size_t len) { return std::string(str, len); } // Usage auto str = "hello"_s; // std::string constexpr UDLs
UDLs can be declared constexpr, enabling compile-time evaluation. This is particularly useful for unit conversions that should happen at compile time.
cpp
constexpr long double operator"" _kg(long double x) { return x * 1000; } // Compile-time constant constexpr long double weight = 3.6_kg; // 3600.0 Standard Library UDLs (C++14)
The C++ standard library defines several UDLs in the std::literals namespace:
| Suffix | Type | Example |
|---|---|---|
s | std::string | "hello"s |
h, min, s, ms, us, ns | std::chrono::duration | 5s, 100ms |
i, il, if | std::complex | 2.0i, 3.0if |
To use them, you must using namespace std::literals; or use fully qualified names like std::literals::string_literals::operator""s.
Full Practical Code Example with Explanation
This example demonstrates user-defined literals for a unit conversion system (kilograms, grams, milligrams) and an imaginary number type. It shows both constexpr and non-constexpr UDLs, and how they improve code readability and safety.
Program: Unit Conversion with UDLs
cpp
/** * unit_conversion.cpp * Demonstrates user-defined literals for mass units and imaginary numbers. * Uses constexpr for compile-time evaluation. */ #include <iostream> #include <iomanip> #include <complex> /* ------------------------------------------------------------------------- * Mass unit literals * ------------------------------------------------------------------------- */ // Gram (base unit) constexpr long double operator"" _g(long double x) { return x; } // Kilogram (1000 grams) constexpr long double operator"" _kg(long double x) { return x * 1000.0L; } // Milligram (0.001 grams) constexpr long double operator"" _mg(long double x) { return x / 1000.0L; } // Pound (453.59237 grams) constexpr long double operator"" _lb(long double x) { return x * 453.59237L; } // Ounce (28.349523125 grams) constexpr long double operator"" _oz(long double x) { return x * 28.349523125L; } /* ------------------------------------------------------------------------- * Imaginary number literal (using std::complex) * ------------------------------------------------------------------------- */ // Imaginary literal: creates a complex number with zero real part constexpr std::complex<double> operator"" _i(long double d) { return std::complex<double>(0.0, static_cast<double>(d)); } /* ------------------------------------------------------------------------- * Main program * ------------------------------------------------------------------------- */ int main() { std::cout << std::fixed << std::setprecision(4); /* Demonstrate mass conversions */ std::cout << "=== Mass Conversions ===\n"; // Convert 3.6 kg to grams long double weight = 3.6_kg; std::cout << "3.6 kg = " << weight << " g\n"; // Add 2.3 mg to 3.6 kg (both converted to grams) long double total = 3.6_kg + 2.3_mg; std::cout << "3.6 kg + 2.3 mg = " << total << " g\n"; // Ratio of 32.3 kg to 2.0 g (both in grams) long double ratio = 32.3_kg / 2.0_g; std::cout << "32.3 kg / 2.0 g = " << ratio << "\n"; // Multiply 32.3 mg by 2.0 g (result in mg*g) long double product = 32.3_mg * 2.0_g; std::cout << "32.3 mg * 2.0 g = " << product << " (mg*g)\n"; // Convert 1.5 lb to grams long double pounds = 1.5_lb; std::cout << "1.5 lb = " << pounds << " g\n"; /* Demonstrate compile-time evaluation with constexpr */ std::cout << "\n=== Compile-Time Evaluation ===\n"; constexpr long double compile_time_weight = 2.5_kg; std::cout << "Compile-time 2.5 kg = " << compile_time_weight << " g\n"; static_assert(2.5_kg == 2500.0L, "Compile-time unit conversion"); std::cout << "static_assert passed: 2.5_kg == 2500.0\n"; /* Demonstrate imaginary numbers */ std::cout << "\n=== Imaginary Numbers ===\n"; std::complex<double> z = 3.0 + 4.0_i; std::complex<double> y = 2.3 + 5.0_i; std::cout << "z = " << z << "\n"; std::cout << "y = " << y << "\n"; std::cout << "z + y = " << z + y << "\n"; std::cout << "z * y = " << z * y << "\n"; std::cout << "abs(z) = " << std::abs(z) << "\n"; /* Demonstrate string literal UDL (C++14) */ std::cout << "\n=== String Literal UDL ===\n"; using namespace std::literals::string_literals; std::string message = "Hello, UDLs!"s; std::cout << message << "\n"; std::cout << "Length: " << message.length() << " characters\n"; return 0; } Explanation of the Code
- Mass unit literals:
_g,_kg,_mg,_lb,_ozare defined asconstexpr long doublefunctions.- They convert the given value to grams (the base unit).
- All conversions happen at compile time if the argument is a constant expression.
- Imaginary literal:
_icreates astd::complex<double>with zero real part and the given imaginary part.constexprallows compile-time construction of complex numbers.
- Main program:
- Demonstrates arithmetic with different units (kg, mg, g, lb).
- Shows compile-time evaluation using
constexprandstatic_assert. - Uses
std::complexfor imaginary numbers. - Uses the standard library string literal
_sforstd::string.
Expected Output
text
=== Mass Conversions === 3.6 kg = 3600.0000 g 3.6 kg + 2.3 mg = 3600.0023 g 32.3 kg / 2.0 g = 16150.0000 32.3 mg * 2.0 g = 0.0646 (mg*g) 1.5 lb = 680.3886 g === Compile-Time Evaluation === Compile-time 2.5 kg = 2500.0000 g static_assert passed: 2.5_kg == 2500.0 === Imaginary Numbers === z = (3,4) y = (2.3,5) z + y = (5.3,9) z * y = (-13.1,24.2) abs(z) = 5 === String Literal UDL === Hello, UDLs! Length: 13 characters
Common Pitfalls, Edge Cases & Memory Considerations
1. Suffix Must Begin with Underscore
User-defined suffixes must start with an underscore (_). Suffixes without an underscore are reserved for the standard library.
cpp
long double operator"" kg(long double x) { // Error: suffix must start with _ return x; } Solution: Always prefix your suffixes with _.
2. Parameter Type Mismatch
The literal operator’s parameter type must match the literal category. For example, an integer literal cannot be passed to an operator expecting long double.
cpp
long double operator"" _kg(long double x) { return x; } auto a = 5_kg; // Error: integer literal cannot be used with long double parameter Solution: Define separate overloads for unsigned long long (integer) and long double (floating-point) if needed.
3. String Literal with Embedded Nulls
Using const char* without a length parameter loses information about embedded null characters.
cpp
std::string operator"" _s(const char* str) { return std::string(str); // stops at first null } auto s = "a\0b"_s; // Only "a" is captured Solution: Use the const char*, std::size_t overload to handle embedded nulls:
cpp
std::string operator"" _s(const char* str, std::size_t len) { return std::string(str, len); } 4. Namespace Pollution
Defining many UDLs in the global namespace can cause name conflicts. Use a dedicated namespace and bring in only what you need.
cpp
namespace units { constexpr long double operator"" _kg(long double x) { return x * 1000; } } using units::operator"" _kg; // Bring into current scope 5. Overloading Ambiguity
Defining both integer and floating-point overloads for the same suffix can lead to ambiguity.
cpp
int operator"" _x(unsigned long long v) { return v * 2; } double operator"" _x(long double v) { return v * 2.0; } auto a = 10_x; // Calls integer version auto b = 10.5_x; // Calls floating-point version This is fine if the argument types are distinct, but be aware that integer literals can be converted to floating-point, causing potential ambiguity.
6. constexpr Limitations
In C++11/14, constexpr functions have restrictions (single return statement in C++11, relaxed in C++14). Complex logic may not be possible in constexpr until C++14/17.
Solution: Use C++14 or later for more flexible constexpr functions, or fall back to non-constexpr when necessary.
7. Memory Considerations
- UDLs do not add runtime overhead if they are
constexpr; values are computed at compile time. - String UDLs that create
std::stringallocate memory on the heap. Usestd::string_view(C++17) for non-owning string literals to avoid allocation. - Integer and floating-point UDLs typically operate on stack values; no heap allocation.
8. Standard Library Suffix Conflicts
The standard library defines suffixes like s (string), h, min, s, ms (chrono durations), and i (complex). If you define your own _s, it may conflict.
Solution: Use distinct suffixes or namespaces to avoid conflicts.
9. Portability
User-defined literals are a C++11 feature. Ensure your compiler supports C++11 or later.
bash
g++ -std=c++11 program.cpp
10. Debugging UDLs
Since UDLs are syntactic sugar for function calls, debugging can be straightforward if you step into the literal operator. However, constexpr UDLs evaluated at compile time may not appear in debuggers.
Solution: Use non-constexpr versions for debugging, or add temporary std::cout statements (though this breaks constexpr).
Summary & Best Practices
User-Defined Literals in C++ provide a powerful mechanism for creating readable, type-safe, and efficient code. By attaching units or semantic meaning directly to literals, they reduce errors and improve maintainability.
Key Takeaways
- UDLs are C++-only; C does not support them.
- They are defined using
operator"" _suffix. - Suffixes must start with an underscore.
- Parameter types are restricted:
unsigned long long,long double,char,const char*,const char*, size_t, and wide/UTF variants. - UDLs can be
constexprfor compile-time evaluation. - Standard library provides UDLs for
std::string,std::chrono, andstd::complex. - They improve readability, type safety, and performance.
Best Practices
- Use UDLs for physical units (e.g.,
_kg,_m,_s) to prevent unit conversion errors. - Always prefix custom suffixes with underscore to avoid standard library conflicts.
- Use
constexprto enable compile-time computation and reduce runtime overhead. - Use the
const char*, size_toverload for string UDLs to handle embedded nulls. - Place UDLs in a namespace to avoid polluting the global namespace.
- Document the units your UDLs represent (e.g.,
_kgreturns grams aslong double). - Prefer standard library UDLs (
s,ms, etc.) when available. - Avoid ambiguous overloads by ensuring parameter types are distinct.
- Test with
static_assertto verify compile-time behavior. - Compile with C++11 or later and use appropriate flags (
-std=c++11,-std=c++14, etc.).
By mastering user-defined literals, you’ll write C++ code that is more expressive, safer, and often faster due to compile-time evaluation.



