Skip to content
C++
Language
since C++11
Basic

User-Defined Literals

Define custom _suffix operators for strong types, units, and compile-time processing. Covers cooked vs raw forms, all operator signatures, and standard UDLs.

User-Defined Literalssince C++11

A mechanism allowing operator""_suffix functions to intercept numeric, character, and string literal tokens so that user-defined types can be constructed directly from literal syntax without any implicit conversion.

Overview

User-defined literals (UDLs) let you write 42_km, 3.14_rad, or "shutdown"_hash and have the compiler call a corresponding operator""_suffix function to produce a typed value. The result is fully typed β€” no silent conversions occur.

UDLs come in two processing forms:

Cooked β€” the compiler tokenizes and validates the literal normally, then passes the interpreted value to your operator. An integer literal becomes unsigned long long; floating-point becomes long double; string literals become const char* plus size_t. This is the common form and should be your default.

Raw β€” the compiler hands you the characters of the numeric token unchanged, either as a null-terminated const char* or as a variadic char template parameter pack. Raw forms apply only to integer and floating-point literals, not to string or character literals. Use raw forms when you need to inspect individual characters β€” for example, parsing binary or big-integer literals that overflow unsigned long long.

For a given numeric literal, the compiler selects operators in this priority order: cooked integer/float form first, then raw const char* form, then the template pack form last. If none of the applicable forms exist, the program is ill-formed.

Naming

All user-defined suffix names must begin with _. Suffixes without a leading underscore are reserved for the standard library β€” using them is undefined behavior. GCC and Clang both warn about this. Standard suffixes (s, sv, h, ms, i, etc.) live in nested inline namespace literals blocks, so callers can do using namespace std::chrono_literals without pulling in the entire std namespace.


Standard Library UDLs

cpp
// C++14 β€” std::string
#include <string>
using namespace std::string_literals;
auto s = "hello"s;              // std::string{"hello"}

// C++17 β€” std::string_view (no allocation)
#include <string_view>
using namespace std::string_view_literals;
auto sv = "hello"sv;            // std::string_view

// C++14 β€” std::chrono durations
#include <chrono>
using namespace std::chrono_literals;
auto t1 = 5h;                   // std::chrono::hours{5}
auto t2 = 30min;                // std::chrono::minutes{30}
auto t3 = 1500ms;               // std::chrono::milliseconds{1500}
auto t4 = 100us;                // std::chrono::microseconds{100}
auto t5 = 42ns;                 // std::chrono::nanoseconds{42}
auto t6 = 0.5s;                 // std::chrono::duration<double>{0.5}

// C++14 β€” std::complex
#include <complex>
using namespace std::complex_literals;
auto c = 1.0 + 2.0i;            // std::complex<double>{1.0, 2.0}

using namespace std::literals pulls in all of the above at once; the set of available UDLs grows with each standard revision (C++14 added string/chrono/complex; C++17 added string_view).


Syntax

cpp
// Integer literals (decimal, octal, hex, binary)
ReturnType operator""_suffix(unsigned long long);               // cooked β€” highest priority

// Floating-point literals
ReturnType operator""_suffix(long double);                      // cooked β€” highest priority

// String literals
ReturnType operator""_suffix(const char*, std::size_t);         // char (default)
ReturnType operator""_suffix(const wchar_t*, std::size_t);      // L"..."
ReturnType operator""_suffix(const char8_t*, std::size_t);      // u8"..." β€” C++20
ReturnType operator""_suffix(const char16_t*, std::size_t);     // u"..."
ReturnType operator""_suffix(const char32_t*, std::size_t);     // U"..."

// Character literals
ReturnType operator""_suffix(char);
ReturnType operator""_suffix(wchar_t);
ReturnType operator""_suffix(char8_t);                          // C++20
ReturnType operator""_suffix(char16_t);
ReturnType operator""_suffix(char32_t);

// Raw numeric forms β€” only for integer and floating-point tokens
ReturnType operator""_suffix(const char*);                      // raw string of token chars
template<char... Cs>
ReturnType operator""_suffix();                                 // template pack β€” lowest priority

When multiple overloads coexist for a numeric literal, cooked wins over raw const char*, which wins over the template form. Define only what you need.


Examples

Physical units with strong types

Providing both the long double and unsigned long long overloads lets callers write 3.5_m and 10_m without a cast.

cpp
#include <cmath>
#include <numbers>  // C++20

struct Meters { double value; };
struct Feet   { double value; };

constexpr Meters operator""_m(long double v)       { return {static_cast<double>(v)}; }
constexpr Meters operator""_m(unsigned long long v) { return {static_cast<double>(v)}; }
constexpr Feet   operator""_ft(long double v)      { return {static_cast<double>(v)}; }
constexpr Feet   operator""_ft(unsigned long long v){ return {static_cast<double>(v)}; }

constexpr Meters to_meters(Feet f) noexcept { return {f.value * 0.3048}; }

constexpr auto track  = 400.0_m;   // Meters{400.0}
constexpr auto hurdle = 3.0_ft;    // Feet{3.0}

// auto bad = track.value + hurdle.value;          // compiles but mixes units silently
auto ok = track.value + to_meters(hurdle).value;   // explicit, correct

Compile-time string hashing

String UDL operators receive a const char* and size_t. Marking the operator constexpr folds the hash to a constant, enabling switch dispatch on strings.

cpp
#include <cstdint>
#include <string_view>

constexpr std::uint32_t fnv1a32(const char* s, std::size_t len) noexcept {
    std::uint32_t h = 2166136261u;
    for (std::size_t i = 0; i < len; ++i)
        h = (h ^ static_cast<unsigned char>(s[i])) * 16777619u;
    return h;
}

constexpr std::uint32_t operator""_hash(const char* s, std::size_t len) noexcept {
    return fnv1a32(s, len);
}

void dispatch(std::string_view cmd) {
    switch (fnv1a32(cmd.data(), cmd.size())) {
        case "open"_hash:  handle_open();  break;
        case "close"_hash: handle_close(); break;
        default:           handle_unknown();
    }
}

Memory-size literals

cpp
#include <cstddef>

constexpr std::size_t operator""_KiB(unsigned long long n) { return n * 1024ULL; }
constexpr std::size_t operator""_MiB(unsigned long long n) { return n * 1024ULL * 1024; }
constexpr std::size_t operator""_GiB(unsigned long long n) { return n * 1024ULL * 1024 * 1024; }

std::vector<char> buf(4_MiB);          // 4,194,304 bytes
static_assert(2_GiB == 2147483648ULL);

Using IEC binary-prefix names (KiB/MiB/GiB) eliminates ambiguity with SI kilobytes.

Raw template form β€” compile-time validation

The template pack form receives each character of the numeric token as a non-type char parameter. This is the only way to validate or process a literal whose value overflows unsigned long long, or to enforce invariants at compile time.

cpp
#include <cstdint>

template<char... Cs>
constexpr std::uint8_t operator""_u8() {
    unsigned value = 0;
    for (char c : {Cs...}) {
        if (c == '\'') continue;    // skip C++14 digit separators
        value = value * 10 + static_cast<unsigned>(c - '0');
    }
    static_assert(value <= 255, "literal exceeds uint8_t range");
    return static_cast<std::uint8_t>(value);
}

constexpr std::uint8_t b = 200_u8;   // OK
// constexpr auto bad = 300_u8;      // static_assert fires at compile time

Note that C++14 digit separators (' inside a literal like 1'000) appear in the character pack and must be explicitly skipped.


Best Practices

Wrap UDLs in inline namespace literals. This mirrors the standard library pattern and lets consumers opt in with using namespace mylib::literals without importing the rest of your namespace.

cpp
namespace mylib {
    inline namespace literals {
        constexpr Meters operator""_m(long double v)   { return {(double)v}; }
        constexpr Feet   operator""_ft(long double v)  { return {(double)v}; }
    }
}

using namespace mylib::literals;  // only UDL operators enter scope

Mark operators constexpr by default. UDL operators are pure functions of their arguments. constexpr enables constant folding and allows use in static_assert, template arguments, and case labels.

Since C++20, consider consteval when the operator must only ever execute at compile time β€” for instance, a UDL that validates a format string or parses a structured value.

cpp
// C++20
consteval std::uint32_t operator""_ipv4(const char* s, std::size_t len) {
    return parse_and_validate_ipv4(s, len);  // compile error if s is not a valid IPv4
}

constexpr auto loopback = "127.0.0.1"_ipv4;  // uint32_t, verified at compile time

Provide both numeric overloads when the suffix makes semantic sense for integers and floats. 10_m and 10.5_m should both work without forcing the caller to write 10.0_m.


Common Pitfalls

Missing _ prefix. Any suffix that does not start with _ is reserved by the standard for future standard-library use. Defining operator""km is undefined behavior. Always write operator""_km. Both GCC (-Wliteral-suffix) and Clang warn about this.

Integer overflow in cooked operators. The compiler always passes unsigned long long, the widest available integer type. If the literal value exceeds ULLONG_MAX, the program is ill-formed. The only escape hatch is the raw template form, which receives raw characters and can implement arbitrary-precision parsing.

The const char* argument to string operators is null-terminated but len excludes the null. The standard guarantees that the pointer points to a null-terminated array of length len + 1, but use the len parameter for all length-sensitive operations β€” do not scan for '\0' to find the end, since embedded nulls are possible in string literals.

using namespace std::literals in headers. This pollutes every translation unit that includes the header. Confine using namespace directives to .cpp files or function bodies, or document explicitly when namespace-scope inclusion is intentional.

Ambiguity with suffixes already used as identifiers. If your codebase has a variable named _m or a macro ending in _m, UDL suffixes can create surprising parse conflicts. Prefer longer, domain-specific names like _meters or _mib to minimize collision risk.


Standard Library UDLs

The standard library ships UDLs for the most common value types, all gated behind inline namespace literals sub-namespaces. This design lets you opt in to just the suffixes you need without polluting global scope.

cpp
// C++14 β€” std::chrono duration suffixes
#include <chrono>
using namespace std::chrono_literals;

auto one_second   = 1s;      // std::chrono::seconds{1}
auto half_ms      = 500ms;   // std::chrono::milliseconds{500}
auto ten_us       = 10us;    // std::chrono::microseconds{10}
auto hundred_ns   = 100ns;   // std::chrono::nanoseconds{100}
auto two_hours    = 2h;      // std::chrono::hours{2}
auto quarter_min  = 0.25min; // std::chrono::duration<double, std::ratio<60>>
cpp
// C++14 β€” std::string and std::complex
#include <string>
#include <complex>
using namespace std::string_literals;
using namespace std::complex_literals;

auto greeting = "hello"s;        // std::string{"hello"} β€” forces heap allocation
auto z        = 1.0 + 2.0i;      // std::complex<double>{1.0, 2.0}
auto zf       = 1.0f + 2.0if;    // std::complex<float>{1.0f, 2.0f}
cpp
// C++17 β€” std::string_view (no allocation, no ownership)
#include <string_view>
using namespace std::string_view_literals;

auto sv = "world"sv;             // std::string_view β€” points into the string literal

using namespace std::literals imports every sub-namespace at once; the set grows with each revision β€” C++14 added string, chrono, and complex; C++17 added string_view. All stdlib UDL operators live inside inline namespace literals (sometimes doubly nested, e.g. std::chrono_literals) so a targeted using namespace std::chrono_literals brings in only the duration suffixes.


Compile-Time Literal Parsing with consteval

consteval forces a UDL operator to execute exclusively at compile time, turning malformed literals into hard compile errors rather than runtime exceptions. This is the right tool when the literal encodes a structured value that can be validated statically.

cpp
// C++20
#include <cstdint>
#include <stdexcept>

consteval std::uint32_t parse_ipv4(const char* s, std::size_t len) {
    std::uint32_t result = 0;
    int octet = 0, dots = 0, digits = 0;
    for (std::size_t i = 0; i <= len; ++i) {
        char c = (i < len) ? s[i] : '.';
        if (c == '.') {
            if (digits == 0 || digits > 3 || octet > 255)
                throw std::invalid_argument{"invalid IPv4"};  // compile error
            result = (result << 8) | static_cast<std::uint32_t>(octet);
            octet = 0; digits = 0; ++dots;
        } else if (c >= '0' && c <= '9') {
            octet = octet * 10 + (c - '0');
            ++digits;
        } else {
            throw std::invalid_argument{"invalid character"};  // compile error
        }
    }
    if (dots != 4) throw std::invalid_argument{"expected 4 octets"};
    return result;
}

consteval std::uint32_t operator""_ip(const char* s, std::size_t len) {
    return parse_ipv4(s, len);
}

constexpr auto loopback = "127.0.0.1"_ip;   // OK: uint32_t = 0x7F000001
// constexpr auto bad    = "999.0.0.1"_ip;  // error: octet > 255 β€” compile time
// constexpr auto also   = "not-an-ip"_ip;  // error: invalid character β€” compile time

The key difference from a constexpr version is that consteval cannot be called at runtime at all β€” any attempt to pass a runtime string yields a compile error. This is stronger than runtime validation because invalid inputs are caught during cmake --build, not in production.


UDL for Strong Typedefs

Strong typedefs prevent silent dimensional errors by making Meters + Seconds a compile error rather than a silent logical bug. UDLs are the ergonomic front-end that makes strong types feel natural to use.

cpp
// C++11
struct Meters  { double value; };
struct Seconds { double value; };

constexpr Meters  operator""_m (long double v)        { return {static_cast<double>(v)}; }
constexpr Meters  operator""_m (unsigned long long v) { return {static_cast<double>(v)}; }
constexpr Seconds operator""_s (long double v)        { return {static_cast<double>(v)}; }
constexpr Seconds operator""_s (unsigned long long v) { return {static_cast<double>(v)}; }

// Arithmetic is only defined within the same dimension
constexpr Meters  operator+(Meters  a, Meters  b) { return {a.value + b.value}; }
constexpr Seconds operator+(Seconds a, Seconds b) { return {a.value + b.value}; }

constexpr auto distance = 100.0_m + 50_m;   // OK: Meters{150.0}
// constexpr auto bad   = 100.0_m + 10_s;   // error: no operator+(Meters, Seconds)

Note that _s conflicts with the C++14 stdlib std::string_literals::operator""s β€” use a longer suffix like _sec in real code to avoid ambiguity. For production-quality dimensional analysis, std::chrono::duration<Rep, Period> is the standard approach: it encodes the unit in the type parameter and provides safe implicit conversions between compatible units at compile time.


Template Literal Operators (C++20)

The string literal template extension lets a UDL suffix embed the entire string content as a template argument, enabling compile-time string-to-type mapping. Each character of the literal becomes a non-type template argument in a char... pack.

cpp
// C++20
#include <array>
#include <cstddef>
#include <algorithm>

// Fixed-size string wrapper β€” usable as a non-type template parameter (C++20 NTTP)
template<std::size_t N>
struct FixedStr {
    char data[N]{};
    consteval FixedStr(const char (&s)[N]) { std::copy_n(s, N, data); }
    consteval bool operator==(const FixedStr&) const = default;
};

// Template literal operator: receives the string as an NTTP via a helper type
template<FixedStr Name>
struct Column {
    static constexpr auto name = Name;
    // In a real ORM, this would generate typed accessor code at compile time.
};

template<FixedStr Name>
constexpr auto operator""_col() { return Column<Name>{}; }

// Usage: each unique string produces a distinct type
auto user_id_col   = "user_id"_col;    // Column<FixedStr{"user_id"}>
auto created_at_col = "created_at"_col; // Column<FixedStr{"created_at"}>

static_assert(!std::is_same_v<decltype(user_id_col), decltype(created_at_col)>);

The auto... / FixedStr NTTP technique is the C++20 replacement for the older template<char... Cs> operator""_name() extension (which was a GCC/Clang extension, never standardized for strings). The string becomes part of the type, so two columns with different names are genuinely different types that cannot be accidentally swapped.


UDL for Binary/Hex Numeric Literals

UDL operators compose on top of C++14's built-in 0b binary prefix and ' digit separators, letting you add range-checking or explicit-width semantics to literals the compiler already parses. The cooked unsigned long long form covers the vast majority of use cases.

cpp
// C++14 β€” built-in binary prefix and digit separators (no UDL required)
constexpr auto flags = 0b1010'1100u;   // unsigned, value = 172

// C++11 β€” UDL for explicit 8-bit width with static_assert guard
constexpr std::uint8_t operator""_u8(unsigned long long v) {
    static_assert(false, "_u8 must be used with a constant");  // fallback; see below
    return static_cast<std::uint8_t>(v);
}

// Better: use the template pack form for compile-time range enforcement
#include <cstdint>
template<char... Cs>
constexpr std::uint8_t operator""_u8() {
    unsigned long long value = 0;
    bool is_binary = false, is_hex = false;
    const char chars[] = {Cs...};
    std::size_t start = 0;
    if (sizeof...(Cs) >= 2 && chars[0] == '0' && (chars[1] == 'b' || chars[1] == 'B'))
        { is_binary = true; start = 2; }
    else if (sizeof...(Cs) >= 2 && chars[0] == '0' && (chars[1] == 'x' || chars[1] == 'X'))
        { is_hex = true; start = 2; }
    for (std::size_t i = start; i < sizeof...(Cs); ++i) {
        char c = chars[i];
        if (c == '\'') continue;  // skip C++14 digit separators
        unsigned digit = is_hex
            ? (c >= 'a' ? c - 'a' + 10 : c >= 'A' ? c - 'A' + 10 : c - '0')
            : is_binary ? (c - '0') : (c - '0');
        value = value * (is_binary ? 2 : is_hex ? 16 : 10) + digit;
    }
    static_assert(value <= 255, "literal exceeds uint8_t range");
    return static_cast<std::uint8_t>(value);
}

// C++14 digit separators work inside UDL template packs β€” they appear as '\'' chars
constexpr std::uint8_t a = 200_u8;          // OK
constexpr std::uint8_t b = 0b1111'1111_u8;  // OK: 255
// constexpr std::uint8_t c = 256_u8;       // static_assert: exceeds uint8_t range
cpp
// C++11 β€” checked 32-bit signed literal via cooked form
#include <cstdint>
#include <limits>

constexpr std::int32_t operator""_i32(unsigned long long v) {
    static_assert(
        v <= static_cast<unsigned long long>(std::numeric_limits<std::int32_t>::max()),
        "literal exceeds int32_t range"
    );
    return static_cast<std::int32_t>(v);
}

constexpr std::int32_t x = 2'147'483'647_i32;  // OK: INT32_MAX with digit separators
// constexpr std::int32_t y = 2'147'483'648_i32; // static_assert fires

C++14's 0b prefix is syntactic sugar that the compiler handles before UDL dispatch β€” when the template pack form is used, the 0, b, and digit characters all appear verbatim in the pack and must be parsed manually.


Namespacing UDLs to Avoid Conflicts

Two libraries defining operator""_s in the global namespace will cause an ambiguous overload error the moment both headers are included. The standard library's inline namespace hierarchy is the established solution, and user libraries should follow the same pattern.

cpp
// C++11 β€” recommended UDL namespace structure
namespace mylib {
    inline namespace literals {
        inline namespace length_literals {
            constexpr Meters operator""_m (long double v)        { return {(double)v}; }
            constexpr Meters operator""_m (unsigned long long v) { return {(double)v}; }
        }
        inline namespace time_literals {
            constexpr Seconds operator""_sec(long double v)        { return {(double)v}; }
            constexpr Seconds operator""_sec(unsigned long long v) { return {(double)v}; }
        }
    }
}

// Consumers can opt in at any granularity:
using namespace mylib::literals;               // all mylib UDLs
using namespace mylib::literals::length_literals; // only _m
cpp
// Conflict scenario: two libraries both define operator""_s
namespace libA { inline namespace literals { std::string operator""_s(const char*, std::size_t); } }
namespace libB { inline namespace literals { Symbol      operator""_s(const char*, std::size_t); } }

using namespace libA::literals;
using namespace libB::literals;

// auto x = "foo"_s;  // error: ambiguous β€” libA::operator""_s vs libB::operator""_s

The inline namespace mechanism means mylib::literals::operator""_m is simultaneously a member of mylib β€” so using namespace mylib also brings in the UDLs. The extra inline namespace literals layer provides the selective opt-in path. Best practice: always use a suffix unique enough to your domain (_mylib_m, or at minimum _m inside a clearly named namespace) and never define UDL operators at global or std scope.


See Also

  • string-literals β€” raw string literals (R"(...)") and encoding prefixes (u8, u, U, L)
  • if-constexpr β€” compile-time branching usable inside constexpr UDL operators
  • structured-bindings β€” another syntax-level facility for integrating user-defined types with language idioms