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.