Halt! ✋ In the name of C++; or why C++ functions don’t need a return keyword

C++ functions do not actually require a return keyword in them to be well-formed and compile, though it will likely cause undefined behaviour.

tl;dr: C++ functions do not require a return keyword in them to be well-formed and compile, though it will likely cause undefined behaviour. Your specific compiler may also warn or error. In almost all cases, you want a return keyword.

C++, a language notorious for its ability to fall over and fail to compile at the slightest of mistakes, has what appears at first glance to be a huge oversight in the specification: a non-void function does not require a return keyword to produce compilable code.

Compiling and running this through GCC 4.6.3 provides the following output:

GCC compiling the code. It warns that there's no return statement, but still prints out a value, 6295648.

Clearly it’s noticed there’s an issue and shown a warning, but has gone ahead anyway and created a runnable executable.

So why is this the case? And what knock-on effects does this have?

The return keyword is never explicitly defined as required to be in a function

Simply put, nowhere in the C++ specification does it say a function must have a return keyword to produce code that compiles. However, it does say that any function with a non-void return type, that reaches the last line (where your closing brace would be), will produce undefined behavior.

Undefined behavior could be anything — the program could crash, it could return garbage values (this is most likely!), the program could even attempt a coup and try to overthrow the local government by force. It’s all up to your compiler to do as it wishes.

We’ll take a look at the specific wording later, but for now let’s consider the impact this has on compilers.

All functions return, so why not just enforce that fact at compile time? 👷

Because not all functions necessarily return.

function5 never returns, strictly speaking. Instead, it throws an exception, the stack is unwound and we travel upwards to find a function that can catch. The return 1; is never reached.

Similarly, function6 exits the program before the return 2.

This all seems dumb. Why doesn’t then compiler just check for those cases? It’s not that hard to ensure all functions return, or throw, or exit right?

Can you guarantee function7 will ever return? All it does is wait for a user to type some input. What if the user never touches their keyboard? How would the compiler know?

What about function8? We, as programmers, know that rand() will never return a number larger than 1 — but the compiler may not know that, especially if rand is included from some unknown, external library.

This is related to the halting problem, an age old computer science question. It asks, in a very simplified form:

Given some function A and an input, can we always write another function B that tells us if A will either terminate, or run forever?

To save you some 200 years of computer science proofs, the answer is, no, you cannot.

As a result of it, if the C++ spec guaranteed all functions must have valid return keywords in all possible paths for all functions, it would be impossible to implement. No matter how many edge cases you try to patch, the halting problem guarantees there will always be some function you cannot compute the full set of return paths for.

By leaving it as undefined behaviour, different compilers can perform sensible (but not necessarily exhaustive of all functions) analysis, and warn / error where they believe return keywords should exist.

So what does the C++ spec actually say? 👓

We’ll get a bit more technical here, however there’s nothing here we haven’t already covered. As per [stmt.return]:

Flowing off the end of a constructor, a destructor, or a function with a cv void return type is equivalent to a return with no operand. Otherwise, flowing off the end of a function other than main results in undefined behavior.

Don’t worry if this very specific and legal wording is off-putting — let’s go through it together.

The C++ specification defines the return statement in this section, [stmt.return], and makes various references to it throughout the rest of the specification. However, nowhere in the specification does it say that a function requires a return keyword in order to create well-formed code. Well-formed code is, in its simplest form, code that will successfully compile.

Surely this means it’s totally safe to ignore return keywords then? No.

“Flowing off” the end of a constructor, destructor, or “cv void” returning function, is the same as putting a return; on the last line. By cv void, it simply means a function with void return type, that may be const or volatile. (This const / volatile distinction is not important.)

Flowing off means reaching the last line (where the closing curly bracket would be), where no more code follows.

As such, the following two functions are identical:

Note that the spec only defines implicitly adding the return for void return functions, constructors, and destructors.

As for flowing off the end of a non-void function, this results in undefined behaviour. This means reaching the end of a function without having hit another jump statement to exit, where a jump statement may be one of break, continue, return, or goto.

What about main? I’ve been told that it’s fine to not have a return keyword in main!

And this is completely true! As per [basic.start.main],

If control flows off the end of the compound-statement of main, the effect is equivalent to a return with operand 0 (see also [except.handle]).

It is explicitly called out in the spec that not having a return keyword in your main function, will implicitly add a return 0;.

A sleepy dog.
https://unsplash.com/photos/azsk_6IMT3I C++ can be a tiring language to get your head around!

What to take away from this article

There’s no specific coding or programming takeaway from this — I certainly wouldn’t recommend using this quirk in your code anywhere. Hopefully it encourages you to do more deep-diving into how C++ works, and you take the opportunity to take a look at the specification.

Let me know on Twitter if you found this interesting, if you have anything to add, or if you believe I’ve made any mistakes in my explanation of the halting problem!

Cheers!

Some further reading references: