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

String Literals and User-Defined Literals

Raw strings, Unicode prefix variants, standard-library UDL suffixes, and custom operator literal — with precise C++ version coverage from C++11 through C++23.

String Literals and User-Defined Literalssince C++11

A string literal is a compile-time constant character sequence embedded in source code; C++11 extended the pre-existing syntax with raw string literals, Unicode encoding prefix variants, and user-defined literal suffixes that dispatch to a programmer-supplied operator"".

Overview

C++ has supported basic string literals since its inception, but C++11 introduced three major extensions: raw string literals that suppress escape processing, Unicode prefix variants that guarantee specific encodings, and user-defined literals (UDLs) that attach type-safe meaning to suffix syntax. The standard library then added its own UDL suffixes in C++14 (s for std::string, duration literals, complex literals) and C++17 (sv for std::string_view).

Adjacent string literals separated only by whitespace are concatenated at compile time — a rule inherited from C and valid since C++98:

cpp
const char* msg = "Hello, "
                  "World!\n";     // same as "Hello, World!\n" — C++98

This is useful for long strings, macro-generated fragments, and mixing raw with regular literals. Concatenation requires compatible encoding prefixes; mixing u8 with u or U is ill-formed. Every string literal has a null terminator appended implicitly: sizeof("abc") is 4, not 3.


Syntax

Prefix Summary

PrefixElement typeEncodingSince
(none)charimplementation-definedC++98
Lwchar_timplementation-defined widthC++98
u8char / char8_tUTF-8C++11 / C++20†
uchar16_tUTF-16C++11
Uchar32_tUTF-32C++11
R"(…)"charno escape processingC++11
u8R"(…)" etc.combinedsee aboveC++11

† In C++11–C++17, u8"…" has type const char*. Since C++20, the element type is const char8_t* — a distinct type from char that silently breaks any code passing u8 literals to const char* parameters.

Raw String Literal Syntax

cpp
R"delimiter(content)delimiter"

delimiter may contain up to 16 characters; valid characters exclude (, ), \, and whitespace. No escape sequences are processed; newlines are literal. The content cannot contain the exact sequence )delimiter", which is why a custom delimiter can be chosen.

cpp
// Default (empty) delimiter
const char* path = R"(C:\Users\Alice\Documents\file.txt)";

// Custom delimiter — needed when content contains `)"` itself
const char* html = R"html(<a href="https://example.com">click</a>)html";

// Multi-line without escaping
const char* payload = R"json({
    "key": "value",
    "nested": { "x": 1 }
})json";

// Regex — eliminates the double-backslash tax
std::regex ipv4{R"(\d{1,3}(?:\.\d{1,3}){3})"};   // C++11

UDL Operator Signature Requirements

The parameter type is fixed by the form of the literal that precedes the suffix:

Literal formRequired parameter signature
42_x(unsigned long long)
3.14_x(long double)
'c'_x(char)
"str"_x(const char*, std::size_t)
u8"str"_x(const char8_t*, std::size_t) — C++20
u"str"_x(const char16_t*, std::size_t)
U"str"_x(const char32_t*, std::size_t)
L"str"_x(const wchar_t*, std::size_t)
Template formtemplate<char...> (numeric literals only)

Examples

Standard Library Suffixes

Use specific sub-namespace using declarations rather than using namespace std::literals to control scope precisely:

cpp
#include <string>
#include <string_view>
#include <chrono>
#include <complex>

using namespace std::string_literals;        // C++14 — enables "..."s
using namespace std::string_view_literals;   // C++17 — enables "..."sv
using namespace std::chrono_literals;        // C++14 — enables duration suffixes
using namespace std::complex_literals;       // C++14 — enables ...i suffixes

auto s   = "hello"s;       // std::string                (C++14)
auto sv  = "hello"sv;      // std::string_view            (C++17)
auto ws  = L"wide"sv;      // std::wstring_view           (C++17)

// Chrono — all C++14
auto ns  = 100ns;          // std::chrono::nanoseconds
auto us  = 500us;          // std::chrono::microseconds
auto ms  = 250ms;          // std::chrono::milliseconds
auto sec = 30s;            // std::chrono::seconds
auto min = 5min;           // std::chrono::minutes
auto h   = 2h;             // std::chrono::hours

// Complex — C++14, in <complex>
auto c1  = 3.0 + 4.0i;    // std::complex<double>
auto c2  = 2.0f + 1.0if;  // std::complex<float>

Custom UDLs for Compile-Time Unit Safety

cpp
// units.hpp
#pragma once
#include <concepts>  // C++20

namespace units {

struct Meters   { double value; };
struct Seconds  { double value; };
struct Velocity { double mps;   };

// inline + constexpr: header-safe and compile-time usable (C++17)
[[nodiscard]] inline constexpr Meters
  operator""_m(long double v) { return {static_cast<double>(v)}; }

[[nodiscard]] inline constexpr Seconds
  operator""_s(long double v) { return {static_cast<double>(v)}; }

[[nodiscard]] inline constexpr Velocity
  operator/(Meters d, Seconds t) { return {d.value / t.value}; }

[[nodiscard]] inline constexpr long long
  operator""_KB(unsigned long long n) { return static_cast<long long>(n) * 1024; }

[[nodiscard]] inline constexpr long long
  operator""_MB(unsigned long long n) { return static_cast<long long>(n) * 1024 * 1024; }

} // namespace units

// main.cpp
using namespace units;

constexpr Velocity light = 299'792'458.0_m / 1.0_s;  // C++14 digit separators
constexpr long long buf  = 64_KB;                      // 65536

// Type system enforces correctness at compile time:
// constexpr Velocity bad = 100.0_m / 50.0_m;  // error: no operator/(Meters, Meters)

Template UDL for Compile-Time Parsing

Template UDLs receive the literal's raw digit characters as a char pack, enabling arbitrary compile-time parsing with zero runtime cost:

cpp
template<char... Cs>
constexpr unsigned long long operator""_b2() {
    unsigned long long result = 0;
    for (char c : {Cs...}) {
        if (c == '\'') continue;   // skip C++14 digit separators
        result = (result << 1) | (c == '1' ? 1ULL : 0ULL);
    }
    return result;
}

static_assert(1010'1100_b2 == 0xAC);   // 172
static_assert(1111'1111_b2 == 255);

C++23 Named Character Escapes

cpp
// \N{Unicode name} — C++23
const char* ae     = "\N{LATIN SMALL LETTER AE}";  // æ
const char* snowman = "\N{SNOWMAN}";               // ☃
const char* pi      = "\N{GREEK SMALL LETTER PI}"; // π

// Useful wherever the Unicode codepoint is hard to recall or type directly

Best Practices

  • Prefer sv over const char* for non-owning string parameters. std::string_view (C++17) carries the length alongside the pointer, avoiding a strlen scan and accepting std::string, char[], and any contiguous range without a conversion copy.
  • Always define UDL operators in a named namespace. Standard suffixes live in std::literals::*; your suffixes should live in your own namespace to prevent ADL surprises and allow callers to opt in with a scoped using declaration.
  • Mark UDL operators inline when defined in headers. Without inline, multiple translation units define the same function and trigger an ODR violation at link time. inline constexpr (C++17) handles both header safety and compile-time evaluation in a single keyword pair.
  • Apply [[nodiscard]] to UDL operators. Discarding a unit-typed return value is almost always a logic error; the attribute surfaces it for free.
  • Use raw string literals for any string containing backslashes or embedded quotes — file paths, regular expressions, SQL, JSON, GLSL, HLSL. The readability gain is immediate and the maintenance burden of escape sequences is eliminated.

Common Pitfalls

cpp
// 1. Space before suffix is a syntax error — the suffix must be adjacent
auto x = 5 _KB;    // error: token '5', then identifier '_KB'
auto y = 5_KB;     // OK

// 2. Negative literals use unary minus, not a negative literal argument
auto neg = -42_KB;  // operator""_KB(42) then unary -; the UDL never sees -42

// 3. Suffixes without a leading underscore are reserved for the standard library
// int operator""km(unsigned long long);   // ill-formed: no underscore

// 4. u8 string type changed in C++20 — a silent source break
void log(const char* msg);
// log(u8"hello");   // OK in C++17; error in C++20 (const char8_t* ≠ const char*)
// Fix: cast explicitly or adopt char8_t throughout

// 5. Raw string delimiters may not contain spaces, backslash, or parentheses
// const char* bad = R" (oops) ";   // ill-formed: space in delimiter

// 6. Concatenating literals with incompatible prefixes is ill-formed
// auto bad = u8"hello" u"world";   // error: UTF-8 + UTF-16
auto ok  = u8"hello" u8" world";    // OK: same encoding

// 7. Importing all of std::literals in a header pollutes every includer
// using namespace std::literals;   // in .hpp: exposes ALL suffixes to all TUs
// Prefer specific sub-namespaces scoped to the .cpp or a local block

Raw String Literals for Regex and JSON

Raw string literals eliminate the double-backslash tax that plagues regular expressions and serialized data formats. Instead of escaping every backslash and quote, you write the pattern exactly as you would in a text editor.

cpp
// C++11
#include <regex>
#include <string>

// Escaped form — every backslash must be doubled
std::regex ipv4_escaped("\\d{1,3}(?:\\.\\d{1,3}){3}");

// Raw form — reads exactly as the regex engine sees it
std::regex ipv4_raw{R"(\d{1,3}(?:\.\d{1,3}){3})"};

// Whitespace + word-boundary pattern
std::regex ws_digits_escaped("pattern\\s+\\d+");
std::regex ws_digits_raw{R"(pattern\s+\d+)"};

Multi-line JSON stored inline benefits equally: no escaping of braces, colons, or inner quotes.

cpp
// C++11
const char* json_payload = R"json({
    "name": "Alice",
    "scores": [10, 20, 30],
    "active": true
})json";

// When the content itself contains `)"`, choose a custom delimiter
// so the closing sequence never appears inside the string
const char* tricky = R"delim(data contains )" inside it)delim";

Raw strings span multiple lines literally — the embedded newlines become part of the string value. The delimiter may be any sequence of up to 16 characters excluding (, ), \, and whitespace.


Unicode String Literals: u8, u, U, L

C++11 added four encoding-prefix variants so that source code can embed text with well-defined encodings rather than relying on implementation-defined behavior. Each prefix determines the element type and the encoding guarantee.

cpp
// C++11 — encoding prefixes
const char*     narrow = "plain ASCII or implementation encoding";
const wchar_t*  wide   = L"wide — wchar_t, platform-specific width";
const char16_t* utf16  = u"UTF-16 encoded text";
const char32_t* utf32  = U"UTF-32 encoded text (one codepoint per element)";
const char*     utf8   = u8"UTF-8 encoded text";   // element type: char (C++11–17)

In C++20 the u8 prefix changed element type from char to the new char8_t, a distinct type introduced specifically to make UTF-8 strings type-safe and distinguishable from narrow strings.

cpp
// C++20 — u8 now yields const char8_t*
const char8_t* utf8_20 = u8"UTF-8 in C++20";  // char8_t, not char

// Passing u8 literals to legacy const char* APIs requires an explicit cast
void legacy_log(const char* msg);
// legacy_log(u8"hello");                         // error in C++20
legacy_log(reinterpret_cast<const char*>(u8"hello"));  // explicit — intentional loss of type info

Source encoding vs execution encoding: the compiler transcodes from the source file encoding (typically UTF-8) to the execution encoding at compile time. With u8, u, and U prefixes the execution encoding is guaranteed regardless of platform; with plain or L literals it is implementation-defined.


String Literal Concatenation

Adjacent string literals separated only by whitespace are joined at compile time into a single literal — this is not string concatenation at runtime and incurs no heap allocation. The rule is inherited from C and has been valid since C++98.

cpp
// C++98 — long strings split across lines for readability
const char* sql =
    "SELECT id, name, email "
    "FROM users "
    "WHERE active = 1 "
    "ORDER BY name ASC;";

// Equivalent to a single literal with all the text concatenated

All adjacent pieces must carry compatible prefixes; mixing incompatible encodings is ill-formed.

cpp
// C++11 — prefix must match across all pieces
const char*     ok1 = "hello" " " "world";        // plain + plain — OK
const char16_t* ok2 = u"hello" u" " u"world";     // UTF-16 + UTF-16 — OK
// const char* bad = u8"hello" u"world";           // ill-formed: UTF-8 + UTF-16

The preprocessor # stringification operator is a different mechanism: #define QUOTE(x) #x turns a macro argument into a string literal at preprocessing time, before adjacent-literal concatenation occurs. These are orthogonal features and do not interact.

cpp
// Preprocessor stringification vs adjacent-literal joining
#define QUOTE(x) #x
const char* a = QUOTE(hello);          // "hello"  — preprocessor
const char* b = "hel" "lo";            // "hello"  — compile-time literal joining
// b is NOT runtime: it does not call operator+ or allocate

constexpr String Processing

std::string_view is the right type for constexpr string parameters because it carries pointer + length without any heap allocation, making it usable in constant-expression contexts. Plain const char* loses the length; std::string (before C++20) allocates and is therefore not constexpr.

cpp
// C++17 — constexpr string_view, compile-time length and searching
#include <string_view>

constexpr std::string_view greeting = "Hello, World!";
static_assert(greeting.size() == 13);
static_assert(greeting[0] == 'H');

// consteval enforces that the call happens only at compile time
consteval std::size_t count_chars(std::string_view sv, char c) noexcept {
    std::size_t n = 0;
    for (char ch : sv) n += (ch == c);
    return n;
}

static_assert(count_chars("banana", 'a') == 3);   // C++20, consteval

A lookup table can be built entirely from a string literal at compile time, avoiding any runtime initialization cost.

cpp
// C++17 — compile-time lookup table derived from a string literal
constexpr std::string_view hex_chars = "0123456789ABCDEF";

constexpr int hex_index(char c) noexcept {
    for (int i = 0; i < 16; ++i)
        if (hex_chars[i] == c) return i;
    return -1;
}

static_assert(hex_index('A') == 10);
static_assert(hex_index('F') == 15);

C++20 permits heap allocation inside constexpr functions, so std::string itself became constexpr — but the allocation must not escape to runtime.

cpp
// C++20 — constexpr std::string (allocation must be transient)
#include <string>

constexpr std::size_t reversed_length(std::string_view sv) {
    std::string tmp(sv.rbegin(), sv.rend());   // heap OK inside constexpr in C++20
    return tmp.size();
}

static_assert(reversed_length("hello") == 5);

String Literals and std::format (C++20)

std::format treats its first argument as a compile-time-checked format string, eliminating the silent undefined behaviour that printf-family functions allow when the format string and argument types mismatch. The format string must be a string literal (or a value of std::format_string<Args...>) so the compiler can validate it against the argument pack.

cpp
// C++20
#include <format>
#include <string>

std::string name = "Alice";
int score = 42;

// std::format — type-safe, no manual concatenation
std::string msg = std::format("Player {} scored {} points.\n", name, score);

// Contrast with fragile manual concatenation — no type checking
std::string old_way = "Player " + name + " scored " + std::to_string(score) + " points.\n";

The first parameter of std::format has type std::format_string<Args...>, which is a wrapper around a consteval constructor that validates placeholders against the argument types at compile time.

cpp
// C++20 — format_string validates at compile time
#include <format>

template<typename... Args>
std::string log_line(std::format_string<Args...> fmt, Args&&... args) {
    return std::format(fmt, std::forward<Args>(args)...);
}

// std::format("{}", 42);       // OK
// std::format("{:d}", "str");  // compile error: 'd' format requires integer

C++23 added std::print and std::println, which write directly to a FILE* or std::ostream without constructing an intermediate std::string.

cpp
// C++23
#include <print>

std::println("Hello, {}!", name);                    // writes to stdout + newline
std::print(stderr, "Error code: {}\n", errno);       // writes to stderr
std::println(std::cout, "Score: {}", score);         // writes to ostream

Security: String Literal Injection and Encoding Pitfalls

String literals in C++ carry no sanitization guarantee: they are raw byte sequences, and composing them with untrusted input by concatenation opens injection vulnerabilities identical to those in C or PHP. The compiler has no concept of "safe output context."

The most common mistake is building HTML or SQL by raw concatenation, where user-supplied values flow directly into the output string:

cpp
// UNSAFE — XSS via HTML construction with raw concatenation
std::string render_greeting(const std::string& user_name) {
    // If user_name = `<script>alert(1)</script>`, the output is executable HTML
    return "<p>Hello, " + user_name + "!</p>";
}

// UNSAFE — SQL injection via string literal + user input
std::string make_query(const std::string& username) {
    // If username = "' OR '1'='1", the WHERE clause is bypassed
    return "SELECT * FROM users WHERE name = '" + username + "'";
}

The correct approach is parameterized queries for databases and explicit escaping or a templating engine for HTML. std::format is safer than + concatenation because it separates data from structure, but it does not escape HTML or SQL — that is still the application's responsibility.

cpp
// SAFER — parameterized query (libpq example)
// C++20
#include <format>

void safe_query(PGconn* conn, const std::string& username) {
    // The $1 placeholder is never concatenated — the driver binds it separately
    const char* command = "SELECT * FROM users WHERE name = $1";
    const char* params[] = { username.c_str() };
    PGresult* res = PQexecParams(conn, command, 1, nullptr, params, nullptr, nullptr, 0);
    // ...
}

// std::format separates format string from data but does NOT HTML-escape
std::string safer_msg = std::format("<p>Hello, {}!</p>", user_name);
// Still unsafe for HTML — you must HTML-escape user_name separately

Never build queries or markup by concatenation. C++ string literals are not security contexts; the type system offers no sanitization. Apply escaping at the boundary where the string enters an external interpreter (SQL engine, HTML parser, shell).


See Also