Inline in C++ (ENG)

Do this at first: Call Stack in C++ (ENG)

Inline Functions

When a function is called, typically, a stack frame is established. To build the frame, the machine needs to do a lot of work, including saving register values, copying parameters, saving the return address, and keeping track of rbp and rsp. After that, the function does its work.

The inline specifier simply tells the compiler to not build this frame, effectively "unboxing" the function code and adding it directly into the caller's frame. This can provide several benefits, such as reducing function call overhead (thus improving performance) and slightly improving readability.

Although the inline specifier declares the function to be inlined, it serves merely just as a suggestion to the compiler. If the compiler finds no need to be inlined, it may not take the suggestion. As a result, in most cases, the inline specifier might not be applied at all.

With GCC optimizations, the compiler can often eliminate the stack frame even without the use of the inline specifier, which is funny.

External Linkage (Not Declared Static)

The inline specifier is particularly useful when dealing with external linkage, whether for inline functions or inline variables (since C++17). The use of inline helps prevent linker errors caused by naming conflicts, which would otherwise violate the One Definition Rule (ODR). This is because inline functions and variables can be defined in multiple translation units without causing conflicts during linking.

To comply with the ODR, the inline specifier comes into play. In the example below, the compiler ensures that the rule is followed by inlining the test function in the test.cpp file during the compilation stage, replacing function calls with the actual function code in the assembly output.

// test.cpp
#include <cstdio>
inline void test() { // This function will not exist in its original form after compilation.
// The compiler will replace calls to this inline function with the actual code during compilation.
    printf("This is an inlined hello in %s\n", __FILE__);
}

void printsomething() {
    test(); // Calls the inline version of the test function defined in this file.
}

// main.cpp
#include <cstdio>
extern void printsomething(); // Declares the external function implemented in test.cpp.
void test() { // This is a regular (non-inline) function definition.
    printf("This is a hello in %s\n", __FILE__);
}

int main() {
    test(); // Calls the test function defined in main.cpp.
    printsomething(); // Calls the printsomething function from test.cpp, which, in turn, calls the inline test function from test.cpp.
    return 0;
}

After compilation, these inline functions and variables are directly inserted into the assembly code at their call sites. Consequently, they do not have separate names in the generated object files, thereby avoiding potential conflicts. However, the inline functions or variables must still be declared in every translation unit where they are used.

Note: A function declared constexpr or consteval in its first declaration is implicitly inline. Similarly, a static data member declared constexpr in its first declaration is implicitly an inline variable.

Inline Variables (Since C++17)

Inline variables are a feature introduced in C++17, which allows variables to be declared with the inline specifier. Inline variables allow variables to be dealt with external linkage like inline functions.

Besides, inline variables are also very useful when dealing with header-only libraries. Some design specifics dictate that the interface (.h/.hpp) should be separate from the implementation (.c/.cpp). Inside a class, when we declare a static variable or function, we are traditionally told not to put the definition (initialization) inside the class, as it is forbidden. However, you can do so if you are using the inline specifier.

Old way:

// .hpp file
class myLib{
	static int counter; // Declareation
	// Other data member
public:
    // Other member functions and definitions
};
// .cpp file
int myLib::counter = 0;
// some other definition and implementation

Header-only library way:

// header_only_library.hpp
// This hpp file contains all the declarations and definitions
// An example to "class as a library"

class myLib {
    // static int counter = 0; // Not allowed, definition must be outside the class
    // const static int counter = 0; // Okay, but requires constexpr for inline
    // const static int counter; // Not allowed, must be initialized (defined)
    inline static int counter = 0; // Okay, inline allows definition inside the class
    // inline static int counter; // Also okay, will be default initialized to 0
    // constexpr static int counter = 0; // Okay, constexpr implies inline
    // inline constexpr static int counter = 0; // Same as above

    // Other data members

public:
    // Other member functions and definitions
};