ODR: Member vs non-member functions
Tags: cpp, odr, onedefinitionrule, cpprules
Published: 2022-06-05 22:09:10

I have always found `inline` keyword in C++ to be a confusing and poorly designed concept. Here is an additional use-case for inline that's designed to obey the one-definition-rule (ODR).

A common C++ operation is to overload the stream insertion operator so you can print your custom types with the expression `output stream << <instance of my type>;` The correct definition for such an overload is this:

```
#ifndef FOO_H
#define FOO_H

#include <iostream>


class Foo {
    public:
        Foo() { m_id = 0; }
    private:
        int m_id;

    friend std::ostream& operator<<(std::ostream& os, Foo& f);
    
};

std::ostream& operator<<(std::ostream& os, const Foo& f) {
    os << f.m_id << std::endl;
    return os;
} 


#endif
```

Now, if in a source file you do `cout << Foo()`, the compiler will happily resolve the call to the friend function overload above.

But, what happens when you include Foo.h in two different source files? You run into the dreaded multiple-definitions error for the function `operator << (ostream&, const Foo&)`.

There are two ways to fix this, create a Foo.cpp and move the definition of `operator << (ostream&, const Foo&)` to that source file and then to build that source file with the rest of your program. The other option is to keep the function definition in the header file but with the addition of the keyword `inline` like below:

```
inline std::ostream& operator<<(std::ostream& os, const Foo& f) {
    os << f.m_id << std::endl;
    return os;
} 
```

The presence of the `inline` keyword tells the compiler to use this definition found in the header file in every translation unit (a source file) where this function is used.  

In essence, if you have a function defined in a header file, it better be an inline function, otherwise when multiple source files include it, you will have multiple definitions of that function that run afoul of the ODR rule. 

Now, you might wonder why the same doesn't apply *to member functions defined in the header file*

For example, let's say we add a new member function called `getId()` to the `Foo` class like below:

```
#ifndef FOO_H
#define FOO_H

#include <iostream>


class Foo {
    public:
        Foo() { m_id = 0; }
        int getId() { return m_id; }
    private:
        int m_id;

    friend std::ostream& operator<<(std::ostream& os, Foo& f);
    
};

inline std::ostream& operator<<(std::ostream& os, const Foo& f) {
    os << f.m_id << std::endl;
    return os;
} 


#endif
```
Now, one would naturally wonder if two translation units calling `getId()` would get two definitions of `getId()` since the same happened when we had `operator <<(ostream&, const Foo&)` defined in the `Foo.h` and when two source files included `Foo.h`. 

But, this is not the case--you can include `Foo.h` in any number of source files and all of them resolve to the single definition of `getId()` defined in `Foo.h`. Why is this? In their infinite wisdom C++ designers choose to *implicitly inline all member functions defined in the class definition*

I still don't know if a function that's implicitly inline is actually inlined when it's invoked. There is this c++ maxim that says "inline is only a hint and the compiler is free to ignore it". Does that apply to implicitly inlined functions? In other words, if I were to stick a 500 line definition for a function that's defined in the class definition, will the compiler still inline it? 

There you have it. If you find `inline` keyword confusing, you're not alone. It makes no sense to me to make a language feature so unintuitive.