Discussion: Off to a Good Start
Execute the code to understand the output and gain insights into function argument evaluation order and its implications.
Run the code
Now, it’s time to execute the code and observe the output.
#include <iostream>struct Logger {};struct Configuration {};Logger initializeLogger(){std::cout << "Initializing logger\n";return Logger{};}Configuration readConfiguration(){std::cout << "Reading configuration\n";return Configuration{};}void startProgram(Logger logger, Configuration configuration){std::cout << "Starting program\n";}int main(){startProgram(initializeLogger(), readConfiguration());}
Understanding the output
In this puzzle, we want to start a program that relies on a logger having been set up and a configuration having been read. As long as both of those are done before the program starts, we’re good. The order in which the logger is set up and the configuration is read doesn’t seem to matter here, but what if it did? What if, for instance, initializing the logger sets up some global logger instance that we use while reading the configuration? Can we rely on the two functions being called in the order listed in the program? Unfortunately, we can’t since the evaluation order of function argument expressions is unspecified.
This is not only theoretical; when we tested this on Linux, GCC went right-to-left and Clang left-to-right. You can see an online demo at Compiler Explorer.
Guarantees and observations
All is not chaos, however; we do have a few guarantees:
Initialization of parameters, including all associated side effects, is guaranteed to have finished before the function starts executing. This guarantees that
initializeLogger
andreadConfiguration
have finished executing beforestartProgram
starts executing, and we don’t, for instance, risk the outputInitializing logger
being interleaved withStarting program
. It sounds obvious, but hey, this is C++, so it’s good to check.The initialization of one parameter, including all associated side effects, is guaranteed to have finished before the initialization of another parameter starts. This guarantees that either
initializeLogger
happens entirely beforereadConfiguration
or vice versa.There is no undefined behavior here; it’s just that the argument expressions can be evaluated in either order.
We know that both initializeLogger
and readConfiguration
happen before startProgram
, just not in which order. Furthermore, we’re not even guaranteed that they happen in the same order each time we run the program, even using the same compiler and the same computer! It’s unlikely that a compiler would generate code where this is non-deterministic, but it would be okay according to the standard. It’s also possible for a compiler to change the ordering in a new version of the compiler if the implementer decides, for instance, that right-to-left is now more efficient than left-to-right.
Calling functions in an order
What can we do about this then? If the ordering is actually important, we can simply call the functions one after the other in the order we desire:
int main(){const auto logger = initializeLogger();const auto configuration = readConfiguration();startProgram(logger, configuration);}
Note that this is not something compilers can warn about. Without some magical ability to know the semantics of the entire program, there’s no way for the compiler to know whether the evaluation order of certain function parameters matters to the program.
Recommendations
Here are some recommendations to avoid undefined behavior and ensure reliable code.
Don’t rely on the order of evaluation of function argument expressions: The order in which function arguments are evaluated is not guaranteed by the C++ standard. Avoid writing code that depends on this order to ensure consistency and avoid potential issues.
Don’t assume evaluation order based on tests: Just because expressions happen to be evaluated in a particular order during testing does not mean that the behavior is reliable. Ensure that your code does not depend on specific evaluation orders to avoid unexpected results across different compilers or platforms.
Level up your interview prep. Join Educative to access 70+ hands-on prep courses.