DEV Community

mdh81
mdh81

Posted on

ODR: Member vs non-member functions

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
Enter fullscreen mode Exit fullscreen mode

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;
} 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)