I see that many still tend to misunderstand the use of the inline keyword, and I do understand them, as I was part of that team years ago.

In this article I’m focusing on free functions, but most of the remarks will be valid for member functions (including those that are implicitly inline), templated functions, and variables too (if you didn’t know, from C++17 you can inline variables as well, with a very similar semantics to functions).

Also, when you see inline please always consider that constexpr implies inline. So, you are currently reading two articles at the price of one! 100% more convenient!

Are you ready? Let’s go. Let’s start from the most important thing.

inline is not for inlining code

Read it again: inline is not for inlining code.

Really.

Code inlining happens more or less independently. I say “more or less” just because I didn’t check the compiler internals myself, but I’m told by am authoritative source (a compiler implementor) that it slightly increases the chances for a function to be inlined. Does the same hold for things that are implicitly assumed inline (i.e. methods declared within a class)? I don’t know.

But, nonetheless, inline is not for inlining.

Let me explain.

Problem

Consider this code:

myfunction.h

#include <iostream>

void myfunction() {
        std::cout << "test" << std::endl;
}

f1.h

void f1();

f1.cpp

#include "myfunction.h"

void f1() {
          myfunction();
}

f2.h

void f2();

f2.cpp

#include "myfunction.h"

void f2() {
          myfunction();
}

main.cpp

#include "f1.h"
#include "f2.h"

int main() {
  f1();
  f2();
}

If we try to compile this, what we get is a linker error. Why? Because we have two implementations of the function myfunction:

f2.cpp.o: in function `myfunction()':
f2.cpp:(.text+0x0): multiple definition of `myfunction()';
f1.cpp.o:f1.cpp:(.text+0x0): first defined here

Now, we have more that one way to solve this.

Proposed solution 1: Move the function body to another translation unit

One would be to move myfunction() definition to a different translation unit (i.e. a cpp file), and leave the declaration in the include file myfunction.h.

For this solution, we just need to change one file, and add another:

myfunction.h

void myfunction();

myfunction.cpp

#include "myfunction.h"
#include <iostream>

void myfunction() {
        std::cout << "test" << std::endl;
}

Technically, you don’t even need to #include "myfunction.h" in this case (but in real cases you probably want to do it anyway).

This solution is suboptimal, because the compiler wouldn’t be able to optimize the call (assuming no link-time optimization, LTO, or link-time code generation, LTCG).

Proposed solution 2: Make the function static

The second option is to inform the linker that the we know we have two function, and it shouldn’t bother, because we want to use the local function only.

This is done using the static keyword (WARNING: this is suboptimal too, and, probably, not what you want… continue reading).

We will only change the include file in this case.

myfunction.h

#include <iostream>

static void myfunction() {
        std::cout << "test" << std::endl;
}

In the final executable we’ll have TWO functions, one in each translation unit, but those functions won’t be exported, so we are avoiding the error.

Proposed solution 3: Make the function inline

The last option is to specify that function inline, this will do something slightly different. Again, we only change the header file.

myfunction.h

#include <iostream>

inline void myfunction() {
        std::cout << "test" << std::endl;
}

This will still leave the symbol public, but the linker will be instructed to only use one of the two function (more or less at random).

This is because we assume the two are, actually, the same function (this is often the correct assumption when the code is defined in a header file).

What’s the right solution?

That’s simple: if your intent is to have one function, either the first or the third are correct.

In addition, if you want to have potentially better code (without using LTO/LTCG), you really want to use inline. In that case, the compiler has all the potential to optimize away your call (effectively inlining the call), but if it decides not to, no error will be generated.

How can I tell the difference?

“It’s all nice, but since there’s no difference, I’ll use static when I don’t want the code to be inlined, and inline when I want inlining to happen.” (off-the-cuff citation, but I heard this so many times…)

No, first of all you can tell the difference from within your code. If we change the code slightly, and we get and return the two function pointers they will differ in the second solution.

But you can tell the difference by exploring what your compiler produces. For example, if you’re on gcc/Linux, this is what you get from nm by executing it on the two files f1.o and f2.o in the three cases (only including the relevant information).

f1.of2.o
Fail (original)00000000 T myfunction()00000000 T myfunction()
New TU (1) U myfunction() U myfunction()
static (2)00000000 t myfunction()00000000 t myfunction()
inline (3)00000000 W myfunction()00000000 W myfunction()
Linker results

So what’s the meaning of that letter before myfunction()?

TThe symbol is in the text (code), and is used globally.
In our original code we have two functions defining the same global (strong) symbol twice, and this causes the fail.
UThe symbol is undefined (that’s why there’s no offset), and it’s something expected to be defined somewhere else, which in the first solution is the new file myfunction.cpp.
This symbol will be resolved at link time
tThe symbol is in the text (code), and is used locally.
In this case, both local symbols are used in their own translation unit, ant they do not conflict globally.
WThe symbol is weak.
Quoting directly from `man nm`, here’s the definition of “weak”:

“When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the symbol is determined in a system-specific manner without error.”

What the man page doesn’t explain is what happen when weak symbols are used together. In that case, only one of the two is picked for everything, no error is generated, and the linking terminates successfully.
Meaning of symbol types

So, the difference between the second and third case is the way the symbol duplication is handled.

What about static inline

That is not a thing.

The two things are conflicting, but the compiler won’t generate an error, and it will take static in my experiments.

(update) I think it’s a deliberate decision to make static inline behave like static, because it’s the most conservative option (see Addendum).

Conclusions

Yes, I cheated. When I babbled about “better code” and “optimizing the code without LTO/LTCG” I really meant “the function will be inlined”, but I did it for a reason: thinking that inline will inline your code is the wrong mental model.

The keyword inline is really for creating weak symbols that gets unified at link time, and this will be even more important for variables. And I know, I should have avoided this last comment – I imagine I’ll have to write an entire new post about inline variables now…

Addendum

Proofreading this, I realized there’s one little thing that I forgot to mention.

What happens if, accidentally, you define two different inline functions with the same name and the same prototype and namespace, in two different translation units? As stated above, those will be two weak symbols, and only one will be picked. This can have unexpected results, so in this case the abuse of inline can backfire (this use-case is indeed the correct specifier would static, to differentiate the two symbols and enable only their local use).

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.