katherinemohr.github.io
A silly little C++ quiz
Published April 20, 2026
Last updated: May 1, 2026
I occasionally shoot myself in the foot think of new questions to add here :)
For all the code snippets, unless otherwise stated,
- assume they are being run on an
x86_64machine withg++and-std=c++23and - assume all necessary headers have been included
Hint: the code snippets might not all compile.
Show all explanations / Hide all explanations
- What is the value of
xafter the following code is executed?int x = 0; x = x++;Answer/Explanation
0First,
x++evaluates to0(returns old value).
Then the increment side effect makesxbecome1, but assignment writes the old0back, so the finalxis0. - What is the value of
xafter the following code is executed?int x[2] = {0,1}; x[0], x[1] = x[1], x[0];Answer/Explanation
{0, 1}The comma operator executes each expression left-to-right, returning the right most value.
x[0]is not executable,x[1] = x[1]is a no-op, andx[0]is not executable, so this does nothing.Why doesn’t this get parsed as
x[0], x[1] = (x[1], x[0]), so the result would be{0, 0}?The assignment operator has higher precedence than the comma operator.
- Given the following function
display, what is the result of passing each of the followingvector<string>s intodisplay? Edit: this question is a bit defunct nowadays, since it looks like this ambiguity is disallowed since GCC 15. If you still want to play along, pretend this is clang.void display(const vector<string> &str_vec) { cout << str_vec.size() << endl; for (const string &str : str_vec) { cout << str << endl; } }vector<string> foo{{"hello", ",", "world"}}; vector<string> bar{{"hello", "world"}}; vector<string> baz{{"hello", "hello"}};Answer/Explanation
There is no
stringconstructor that takes in 3const char*s as input, sofoois actually constructed as the list["hello", ",", "world"], anddisplay(foo)results in:3 hello , worlddisplay(bar)results in an error because clang tries to call thetemplate< class InputIt > basic_string( InputIt first, InputIt last, const Allocator& alloc = Allocator() );constructor with
"hello"and"world” as the arguments. These two strings are likely stored in unrelated locations in the code, so when clang tries to traverse fromfirsttolast, it causes a runtime error.display(baz)results in:1(probably, maybe, depends on the implementation…).
clang will call the same constructor as above, but this time,
"hello"and"hello"should be the same pointer, so when clang iterates between them, it just makes the empty string"". - What is the output of
int foo = 1; cout << foo["bar"] << endl;Answer/Explanation
aarray_ptr[index]andindex[array_ptr]both desugar to just*(array_ptr + index)so either syntax does the same thing, and
"bar"[1] == "a" - What is the output of the following?
struct Bit { int b : 2 = 1; }; int main() { Bit bit; cout << ++bit.b << endl; }Answer/Explanation
-2Here,
bis a bit-field with an explicit width of 2 bits. Becausebis anint(notunsigned), the possible range of values is[-2, 1], so when we incrementbfrom 1, it overflows back to-2. - What does
constmodify in the following: theint, or the pointer to theint?int const *ptr;Answer/Explanation
The rule for
constis:const modifies what is on its left. Unless there is nothing on its left, in which case it modifies what’s on its right.
So, the
consthere modifiesint.This rule can lead to confusion, which has created two opposing style preferences for how to
constyour types, known as the “East Const” vs “West Const” style. There are plenty of discussions about this online, but I’ll just link this one. - What is the output of the following? Why?
void print(int &x) { cout << 1; } void print(int &&x) { cout << 2; } void print(const int &x) { cout << 3; } void print(const int &&x) { cout << 4; } int main() { int a = 3; print((const int)a); print((const int)1); print(4); }Answer/Explanation
222
In each case, the argument is temporary, either because the argument is a literal or because of the casting. As such, each argument here is a prvalue.
A temporary can’t bind to an lvalue reference (aka
int &), so they bind to rvalue references instead (akaint &&).The
constgoes missing here becase, as stated in the cpp standard,“If a prvalue initially has the type cv T, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.”
intis a built-in, so theconstcan be dropped. - What is the output of the following? Why?
void print(string &x) { cout << 1; } void print(string &&x) { cout << 2; } void print(const string &x) { cout << 3; } void print(const string &&x) { cout << 4; } int main() { string a = "foo"; print((const string)a); print((const string)"bar"); print("baz"); }Answer/Explanation
442
In each case, the argument is temporary, either because the argument is a literal or because of the casting. As such, each argument here is a prvalue.
A temporary can’t bind to a non-const lvalue reference (aka
int &), so they bind to rvalue references instead (akaint &&).Unlike the previous question,
stringis a class type, so theconstdoesn’t get dropped. - Given the empty struct
struct foo {}, what is the size offoo?Answer/Explanation
1 byte.
(Note that in C, this would be 0.)
From the C++ standard,
“A class with an empty sequence of members and base class objects is an empty class. Complete objects and member subobjects of an empty class type shall have nonzero size.”
- Given the struct below, what is the size of
foo?struct foo { uint8_t a; uint16_t b; uint8_t c; uint32_t d; uint8_t e; };Answer/Explanation
16 bytes
I’m actually going to link the godbolt link before the explanation this time, since that might just explain it better than me. (Pro tip: the
paholefunction in godbolt is super helpful for struct packing!)Here is a longer post about structure packing (technically for C, but it also mostly applies to C++), but tl;dr, each member wants to be aligned to its width, so we get the following calculation:
struct foo { // a is 1 byte and takes up byte 0 of the struct uint8_t a; // b is 2 bytes, so it must be aligned to an even byte, // and it takes up bytes 2-3 of the struct uint16_t b; // c is 1 byte and takes up byte 4 of the struct uint8_t c; // d is 4 bytes and must be 4-byte aligned, // so it takes up bytes 8-11 of the struct uint32_t d; // e is 1 byte and takes up byte 12 of the struct uint8_t e; // a struct has the alignment of its widest member, which is 4 here, // so we round up from 13 to 16 }; - Following up on that, given the same struct below, what is the minimum size it could be? How would you need to reorder the members to achieve that size?
struct foo { uint8_t a; uint16_t b; uint8_t c; uint32_t d; uint8_t e; };Answer/Explanation
12 bytes
There are several possible reorderings, so here is just one of them.
- What is the output of the following code?
#include <iostream> void f() { auto counter = []() { static int b = 1; return b++; }; std::cout << counter(); } int main() { f(); f(); }Answer/Explanation
12This one is very straightforward. As you’d expect (and hope),
staticvariables in lambda functions are initialized once and persisted. - What is the output of the following code?
#include <iostream> struct A { A() { std::cout << "A\n"; } ~A() { std::cout << "~A\n"; } }; int main() { std::cout << sizeof new A << std::endl; }Answer/Explanation
8As you’d expect,
sizeof (A*)is 8 bytes, but why don’t we seeAget constructed and destructed?“The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is an unevaluated operand…“
so
new Anever actually gets evaluated! - What is the output of the following code?
#include <iostream> bool _false() { std::cout << "false"; return false; } bool _true() { std::cout << "true"; return true; } int bar() { std::cout << "bar"; return 0; } char baz() { std::cout << "baz"; return 0; } int main() { return _false() && _true() ? bar() : baz(); }Answer/Explanation
falsebazAnother simpler one, just to check that you know the conditional will early exit and ignore
_true()sincefalse &&anything is justfalse. - In the following code (assuming no optimizations, ie
-O0):- How many times is
foo’s parameterized constructor called? - How many times is
foo’s copy constructor called?
struct foo { int x; // Parameterized constructor foo(int _x) : x(_x) {} // Copy constructor foo(foo &other) : x(other.x) {} }; struct bar { foo x; foo y; bar(foo _x, foo _y) : x(_x), y(_y) {} }; int main() { foo x{1}; foo y{2}; bar{x,y}; return 0; }Answer/Explanation
- The parameterized constructor is called twice, for
foo x{1}andfoo y{2}. - The copy constructor is called four times, to construct
bar’s parameters_xand_yand then to construct its member variablesxandy.
With optimizations, none of the constructors would ever be called because all the
fooandbarcode is dead, but even ignoring that, copy elision will reduce the number of copy constructions to 2 since“Since C++17, a prvalue is not materialized until needed, and then it is constructed directly into the storage of its final destination”
- How many times is
- Identify each stack and heap allocation in the following:
#include <memory> int main() { int x = 42; int *y = new int(8); auto z = std::make_shared<int>(-50); int result = x + *y + *z; delete y; return result; }Answer/Explanation
Stack allocations:
int xint *y(specifically the pointer, not the data)shared_ptr<int> z(specifically the handle)result
Heap allocations:
int(8)int(-50)and the correspondingshared_ptrcontrol block- in just one heap allocation because
make_sharedwas used!
- in just one heap allocation because
- What does the following print out?
int x = 010; std::cout << x << std::endl;Answer/Explanation
8C++ treats numbers with preceding
0s as octal, and10in octal equals8in decimal. - When does each variable get allocated and deallocated?
int a = 0; void foo() { int b = 1; static int c = 2; { int d = 3; static int e = 4; const int f = 5; } } int main() { foo(); return 0; }Answer/Explanation
This is a storage duration question!
a,c, andehave static storage durations, so they are allocated at the start of the program and deallocated at the end of the program. (aisn’t declaredstaticbut is one by default by virtue of being global.)Everything else (
b,d, andf) has automatic storage durations, so they are allocated at the start of their block scope and deallocated at the end of the block scope. - What does the following code evaluate to? Why might we choose to write code this way?
#include <iostream> template<typename T> auto f(T t) -> decltype(t.foo(), int{}) { return 1; } template<typename T> auto f(T t) -> decltype(t + 1, int{}) { return 2; } int f(...) { return 3; } struct A { void foo(); }; struct B {}; int main() { std::cout << f(A{}) << f(B{}) << f("hi"); }Answer/Explanation
132This is an example of our good friend SFINAE.
As implied by the expansion of SFINAE (substitution failure is not an error), if an error occurs while trying to substitute in an argument for some template, the compiler will just ignore that function overload instead of erroring.
We can use this to our advantage by writing code that intentionally errors when we try to substitute in certain types for template parameters. There are a number of ways to do this, but we use
decltype([condition to satisfy], int{}). If the[condition to satisfy]succeeds, the comma operator moves on and returnsdecltype(int{}), which is the correct output type. And if it errors, the function overload is ignored.Here,
- the first function overload requires that the type
Thas afoomember function, which onlyAhas. - the second function overload requires that the type
Thas anoperator+, which only thestring "hi"has. - the final function overload accepts everything, so
Bends up calling this.
For fans of C++20 and beyond, this can be done in a much cleaner way with concepts. Here is this code translated to use concepts instead:
template<typename T> concept HasFoo = requires(T t) { t.foo(); }; template<typename T> concept CanAddInt = requires(T t) { t + 1; }; template<HasFoo T> int f(T t) { return 1; } template<CanAddInt T> int f(T t) { return 2; } int f(...) { return 3; } - the first function overload requires that the type