The Structure of the Puzzles
Learn the structure of the puzzles, the learning methodology, some key points, and the undefined behavior of a few puzzles.
This course contains 25 C++ puzzles with answers and explanations. Most will be well-formed programs, which the C++ standard guarantees output. Some might, however, have a compilation error, and some might have undefined or unspecified behavior. Your task is to figure out what happens when you compile and run the program in each puzzle on a conforming C++ implementation.
Sample puzzle
Take this puzzle as an example. It’s a complete C++ program with a main()
function:
#include <iostream>int main(){std::cout << (1 < 2);}
Your task(s)
Your task will be to:
Read through the code and try to guess the output when the program is compiled and run.
Always ensure you give it a proper go before moving to the explanation lesson to look at the answer!
Key points to remember
Note a few technicalities:
For brevity, we declare the
main()
function with no parameters (noargc
/argv
) and don’t explicitly return a value. These are all optional; omitting them makes the puzzles slightly easier to read.We use
struct
instead ofclass
for all the puzzles. There’s no semantic difference—struct
just haspublic
rather thanprivate
as the default visibility for members, so we don’t have to putpublic:
everywhere.As always in C++, a
bool
is printed as1
and0
by default, nottrue
andfalse
.
Note that all the puzzles in this course are based on the standards of C++17.
Answer with explanation
Let’s execute the code and observe the output.
#include <iostream>int main(){std::cout << (1 < 2);}
The answer to the above puzzle is that 1
is less than 2
, so the program prints 1
(representing true
).
Learning methodology: Guess, run, understand
Getting the answers right is only half the fun, though. The other half is understanding why it works the way it does. The puzzles are excuses to learn more about how C++ works under the hood. Before moving on to the answer and explanation:
Go ahead and guess the output.
After guessing, we encourage you to run the code and see the output yourself.
Then, read the solution and the explanation thoroughly to understand them.
Undefined behavior
Some puzzles might have undefined behavior. Undefined behavior is the term used when something bad happens during the execution of a program, which the compiler is unable to (or, more specifically, not required to) detect. For instance, we might access an element past the end of an array, or an arithmetic expression with signed integers might overflow. In these cases, the C++ standard imposes no restrictions on the implementation, and anything can happen, including nasal demons. If a puzzle has undefined behavior, your task is to identify the undefined behavior but also to guess what happens inpractice on a typical system. Does it actually make demons fly out of your nose, or does something specific happen?
Example
Again, let’s look at an example:
#include <iostream>#include <limits>int main(){std::cout << std::numeric_limits<int>::max() + 1;}
Signed integer overflow in arithmetic is an undefined behavior, so if you identified that, you’re halfway there!
How to tackle
Making assumptions about what will happen in the case of undefined behavior is a bad idea. So let’s do that next! For any puzzles with undefined behavior, the other half of the puzzle is to figure out what would happen if you run the program on your computer. For instance, consider a computer that uses two’s complement for signed integers (as all conforming implementations do since C++20) and where the CPU doesn’t generate an exception when an overflow occurs. So when we add 1
to the largest positive integer, the value simply wraps around to the smallest negative integer. Since the system uses 32-bit int
, the program prints -2147483648
. You don’t need to know that exact value, but if you guessed it would print the smallest negative integer, you solved it!
Don’t do this at home
Guessing or testing what happens in the case of undefined behavior is an interesting exercise that can teach you more about how C++ works on your platform. It might also enable you to recognize certain error patterns when you see them happening in real programs. But do not make any assumptions about your real programs based on what you find! Your assumptions might be untrue on other computers, after upgrading your compiler, or when you compile with different optimization settings. The compiler is even allowed to remove error checking from your code if it can prove that there’s undefined behavior!
Unspecified and implementation-defined behavior
The C++ standard doesn’t specify everything strictly; it leaves some freedom to the implementation. These are some examples:
The specific sizes of integer types
The order of evaluation of function arguments
The order of initialization of global variables
This allows each implementation to make choices that make the most sense on that particular system.
Most programs have some unspecified or implementation-defined behavior; this is not a bug. And contrary to undefined behavior, demons will not fly out of your nose. It’s just that different implementations might behave a bit differently within a set of allowed behaviors.
If a puzzle has unspecified or implementation-defined behavior, try to also guess what a typical behavior would be.