Home > Software design >  How is the inline specifier used in C to preserve the one definition rule?
How is the inline specifier used in C to preserve the one definition rule?

Time:11-15

I've been trying to figure out how the inline specifier preserves ODR. So far, with everything I've written it seems unnecessary because include guards ensure that definitions are only included once.

Suppose I have the following definition in a file called constants.h

#ifndef CONSTANTS_H
#define CONSTANTS_H

namespace constants {
    inline const double pi { 3.14159255358979323846 };
    inline const double e  { 2.71828182845904523536 };
}

#endif

From my understanding of inline in relation to the ODR, the inline specifier is written to ensure that the definition of these constants are only initialized once across multiple translation units. So if I include this file in a.cpp and b.cpp everything should be good.

Now, let's remove the inline keyword.

#ifndef CONSTANTS_H
#define CONSTANTS_H

namespace constants {
    const double pi { 3.14159255358979323846 };
    const double e  { 2.71828182845904523536 };
}

#endif

Now if I include this in a.cpp and b.cpp no issues. I guess this is because of the include guards which ensure multiple definitions of the same thing don't occur twice.

Next, let's remove the include guards

namespace constants {
    const double pi { 3.14159255358979323846 };
    const double e  { 2.71828182845904523536 };
}

Still no problem. Maybe because const qualified variable definitions have internal linkage by default. As a result, including constants.h in a.cpp and b.cpp makes each of these definitions internal by default to their respective translation units.

Having a hard time breaking ODR across multiple translation units. Let's remove const now.

namespace constants {
    double pi { 3.14159255358979323846 };
    double e  { 2.71828182845904523536 };
}

NOW! ODR is broken across multiple translation units. Let's try fixing this with inline so that the compiler knows to only define these variables once.

namespace constants {
    inline double pi { 3.14159255358979323846 };
    inline double e  { 2.71828182845904523536 };
}

Ok, no more errors, this file can once again be included in multiple translation units. So why is considered "best practice" to declare constants in header files as inline? It seems to take a lot of effort to break ODR and inline is redundant in the presence of include guards.

CodePudding user response:

Constants that are not declared with the specifier extern have internal linkage.

So all compilation units that include these declarations

namespace constants {
    const double pi { 3.14159255358979323846 };
    const double e  { 2.71828182845904523536 };
}

have their own constants pi and e.

From the C 14 Standard (3.5 Program and linkage)

3 A name having namespace scope (3.3.6) has internal linkage if it is the name of

(3.2) — a variable of non-volatile const-qualified type that is neither explicitly declared extern nor previously declared to have external linkage; or

Opposite to the above declarations these declarations

namespace constants {
    double pi { 3.14159255358979323846 };
    double e  { 2.71828182845904523536 };
}

have external linkage. So the compiler issues an error if these declarations (that are also definitions) are included in more than one compilation unit because the one definition rule is broken.

You could make the above variables to have the internal linkage if you declare them in an unnamed namespace as for example

namespace constants {
    namespace {
        double pi { 3.14159255358979323846 };
        double e  { 2.71828182845904523536 };
    }
}

As for these declarations

namespace constants {
    inline double pi { 3.14159255358979323846 };
    inline double e  { 2.71828182845904523536 };
}

then inline variables with external linkage may be defined in more than one compilation unit. Moreover an inline variable shall be defined in each compilation unit where it is ODR-used.

  • Related