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++11A 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:
const char* msg = "Hello, "
"World!\n"; // same as "Hello, World!\n" — C++98This 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
| Prefix | Element type | Encoding | Since |
|---|---|---|---|
| (none) | char | implementation-defined | C++98 |
L | wchar_t | implementation-defined width | C++98 |
u8 | char / char8_t | UTF-8 | C++11 / C++20† |
u | char16_t | UTF-16 | C++11 |
U | char32_t | UTF-32 | C++11 |
R"(…)" | char | no escape processing | C++11 |
u8R"(…)" etc. | combined | see above | C++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
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.
// 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++11UDL Operator Signature Requirements
The parameter type is fixed by the form of the literal that precedes the suffix:
| Literal form | Required 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 form | template<char...> (numeric literals only) |
Examples
Standard Library Suffixes
Use specific sub-namespace using declarations rather than using namespace std::literals to control scope precisely:
#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
// 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:
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
// \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 directlyBest Practices
- Prefer
svoverconst char*for non-owning string parameters.std::string_view(C++17) carries the length alongside the pointer, avoiding astrlenscan and acceptingstd::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 scopedusingdeclaration. - Mark UDL operators
inlinewhen defined in headers. Withoutinline, 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
// 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 blockRaw 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.
// 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.
// 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.
// 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.
// 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 infoSource 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.
// 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 concatenatedAll adjacent pieces must carry compatible prefixes; mixing incompatible encodings is ill-formed.
// 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-16The 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.
// 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 allocateconstexpr 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.
// 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, constevalA lookup table can be built entirely from a string literal at compile time, avoiding any runtime initialization cost.
// 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.
// 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.
// 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.
// 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 integerC++23 added std::print and std::println, which write directly to a FILE* or std::ostream without constructing an intermediate std::string.
// 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 ostreamSecurity: 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:
// 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.
// 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 separatelyNever 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
std::string_view— non-owning string referenceconstexpr— compile-time evaluation contextsautotype deduction — how suffix-inferred types are deducedstd::format/std::print— C++20/23 string formatting