I will try to explain my "secret understanding" the best way I can.
There are two entirely separate concepts here. One is the compiler's ability to replace a function call by repeating the function body directly at the call site. The other is the possibility of defining a function in more than one translation unit (= more than one .cpp
file).
The first one is called function inlining. The second is the purpose of the inline
keyword. Historically, the inline
keyword was also a strong suggestion to the compiler that it should inline the function marked inline
. As compilers became better at optimising, this functionality has receded, and using inline
as a suggestion to inline a function is indeed obsolete. The compiler will happily ignore it and inline something else entirely if it finds that's a better optimisation.
I hope we've dealt with the explicit inline
–inlining relationship. There is none in current code.
So, what is the actual purpose of the inline
keyword? It's simple: a function marked inline
can be defined in more than one translation unit without violating the One Definition Rule (ODR). Imagine these two files:
file1.cpp
int f() { return 42; }
int main()
{ return f(); }
file2.cpp
int f() { return 42; }
This command:
> gcc file1.cpp file2.cpp
Will produce a linker error, complaining that the symbol f
is defined twice.
However, if you mark a function with the inline
keyword, it specifically tells the compiler & linker: "You guys make sure that multiple identical definitions of this function do not result in any errors!"
So the following will work:
file1.cpp
inline int f() { return 42; }
int main()
{ return f(); }
file2.cpp
inline int f() { return 42; }
Compiling and linking these two files together will not produce any linker errors.
Notice that of course the definition of f
doesn't have to be in the files verbatim. It can come from an #include
d header file instead:
f.hpp
inline int f() { return 42; }
file1.cpp
#include "f.hpp"
int main()
{ return f(); }
file2.cpp
#include "f.hpp"
Basically, to be able to write a function definition into a header file, you have to mark it as inline
, otherwise it will lead to multiple definition errors.
The last piece of the puzzle is: why is the keyword actually spelled inline
when it has nothing to do with inlining? The reason is simple: to inline a function (that is, to replace a call to it by repeating its body on the call site), the compiler must have the function's body in the first place.
C++ follows a separate compilation model, where the compiler doesn't have access to object files other than the one it's currently producing. Therefore, to be able to inline a function, its definition must be part of the current translation unit. If you want to be able to inline it in more than one translation unit, its definition has to be in all of them. Normally, this would lead to a multiple definition error. So if you put your function in a header and #include
its definition everywhere to enable its inlining everywhere, you have to mark it as inline
to prevent multiple definition errors.
Notice that even today, while a compiler will inline any function is sees fit, it must still have access to that function's definition. So while the inline
keyword is not required as the hint "please inline this," you may still find you need to use it to enable the compiler to do the inlining if it chooses to do so. Without it, you might not be able to get the definition into the translation unit, and without the definition, the compiler simply cannot inline the function.
The compiler cannot. The linker can. Modern optimisation techniques include Link-Time Code Generation (a.k.a. Whole Program Optimisation), where the optimiser is run over all object files as part of the linking process, before the actual linking. In this step, all function definitions are of course available and inlining is perfectly possible without a single inline
keyword being used anywhere in the program. But this optimisation is generally costly in build time, especially for large projects. With this in mind, relying solely on LTCG for inlining may not be the best option.
For completeness: I've cheated slightly in the first part. The ODR property is actually not a property of the inline
keyword, but of inline functions (which is a term of the language). The rules for inline functions are:
- Can be defined in multiple translation units without causing linker errors
- Must be defined in every translation unit in which it is used
- All its definitions must be token-for-token and entity-for-entity identical
The inline
keyword turns a function into an inline function. Another way to mark a function as inline is to define (not just declare) it directly in a class definition. Such a function is inline automatically, even without the inline
keyword.