Card 1 / 4
C++ has five kinds of built-in literals — integer (42), floating-point (3.5), character ('a'), string ("hi") and boolean (true) — plus nullptr. Their types are fixed by the language, and a suffix changes them: 42u is unsigned int, 42L is long, 3.5f is float, 1.0L is long double, and 'a' is char (in C it would be int).
The string literal is the surprising one:
| literal | type | note |
|---|---|---|
"hi" |
const char[3] |
two characters plus the terminating '\0' |
"hi"s (with using namespace std::literals;) |
std::string |
a standard user-defined literal |
"hi"sv |
std::string_view |
non-owning view over the array |
L"hi" |
const wchar_t[3] |
wide string |
The array decays to const char* the moment you pass it somewhere that expects a pointer, which is why "hi" looks like a pointer in most code. sizeof("hi") is 3 while sizeof(const char*) is 8 — the difference is one way to see the real type.
What is the type of the literal "hi" in C++?
const char[3]Correct answerCorrect. Two characters plus the implicit '\0' terminator, stored as a read-only array. sizeof("hi") == 3 confirms it.
const char*Incorrect — that is what the literal decays to when passed to a function or assigned to a pointer, not its own type. auto p = "hi"; gives you the decayed pointer, which is where the confusion comes from.
std::stringIncorrect. A plain string literal is a C-style array; you get a std::string only by constructing one or by using the s suffix ("hi"s).
char[2]Incorrect on two counts: the terminating '\0' is part of the array (so 3, not 2), and the elements are const — modifying a string literal is undefined behaviour.
Card 2 / 4
A user-defined literal (UDL) is a suffix you define so values carry their unit in the source: 90.0_deg. Write two: _deg converts degrees to radians, _rad returns radians unchanged. Both return double.
| literal | result |
|---|---|
180.0_deg |
3.14159265 |
90.0_deg |
1.57079633 |
0.0_deg |
0.0 |
1.5_rad |
1.5 |
90.0_deg + 1.57079633_rad |
3.14159265 — the results are plain doubles and mix freely |
The declaration shape is constexpr double operator""_deg(long double degrees). Two rules: the suffix must start with an underscore (suffixes without one are reserved for the standard library, e.g. 10ms, "x"s), and a floating-point literal operator may take only long double — the compiler hands you the literal at full precision. Convert with degrees * pi / 180; kPi is defined above the editable region.
// Two user-defined literals so that angles read naturally in source:
// 90.0_deg -> converts degrees to radians
// 1.5_rad -> already radians, returned unchanged
// Rules for a floating-point literal operator: the name starts with `_`,
// and the ONLY parameter type allowed is `long double`.
constexpr double operator""_deg(long double degrees) {
return static_cast<double>(degrees * kPi / 180.0L); // 180 degrees == pi radians
}
constexpr double operator""_rad(long double radians) {
return static_cast<double>(radians); // identity: the suffix documents the unit
}Card 3 / 4
Write two integer user-defined literals so buffer sizes read as 64_KiB and 2_MiB instead of 65536 and 2097152. Both return std::size_t.
| literal | result |
|---|---|
1_KiB |
1024 |
4_KiB |
4096 |
0_KiB |
0 |
1_MiB |
1048576 |
64_KiB + 1_MiB |
1114112 — plain integers, they add like any other |
An integer literal operator has one allowed parameter type: unsigned long long — constexpr std::size_t operator""_KiB(unsigned long long n) { return n * 1024; }. (Floating literals get long double; a suffix that must see the raw digits takes const char*.) Making it constexpr lets 2_KiB appear where a constant is required, such as an array bound or a static_assert.
// Byte-size literals: 4_KiB == 4096, 2_MiB == 2 * 1024 * 1024.
// Rules for an INTEGER literal operator: the name starts with `_`, and the
// only parameter type allowed is `unsigned long long`. Return std::size_t.
constexpr std::size_t operator""_KiB(unsigned long long n) {
return static_cast<std::size_t>(n) * 1024;
}
constexpr std::size_t operator""_MiB(unsigned long long n) {
return static_cast<std::size_t>(n) * 1024 * 1024;
}Card 4 / 4
A constexpr function is an ordinary function that the compiler is also allowed to evaluate while compiling — whenever every argument is a constant. Write an iterative constexpr fib(n) (Fibonacci: fib(0) = 0, fib(1) = 1, each later value the sum of the two before).
n |
fib(n) |
|---|---|
0 |
0 |
1 |
1 |
10 |
55 |
30 |
832040 |
50 |
12586269025 |
The same fib runs in two worlds:
constexpr long long a = fib(3); // compile time: 3 is a constant, so the compiler
// computes 2 and bakes it into the binary
int k = 3;
long long b = fib(k); // run time: k is a variable — an ordinary function callSince C++14 a constexpr body may contain local variables and loops:
long long a = 0, b = 1; // fib(i) and fib(i + 1)
for (int i = 0; i < n; ++i) {
long long next = a + b;
a = b;
b = next;
}
return a;Use long long: fib(50) overflows int.
The tests use static_assert(condition, "message"): a check the compiler runs — if the condition is false, the build fails with that message. Its condition must be computable at compile time, so static_assert(fib(10) == 55, "...") passing is proof that the compiler evaluated fib(10). A constexpr variable (constexpr long long f3 = fib(3);) forces compile-time evaluation the same way.
// A constexpr function is an ordinary function that the compiler may ALSO
// evaluate while compiling, whenever every argument is a constant:
// constexpr long long a = fib(3); // compile time
// int k = 3; long long b = fib(k); // run time — k is a variable
// Write an iterative constexpr fib(n): fib(0)=0, fib(1)=1, then each value is
// the sum of the two before. Loops and locals are allowed since C++14.
constexpr long long fib(int n) {
long long a = 0, b = 1; // fib(i) and fib(i + 1)
for (int i = 0; i < n; ++i) {
long long next = a + b;
a = b;
b = next;
}
return a;
}