Maybe a few examples would help.
1. Traditional compiled library
foo.h:
extern int x;
int * f();
foo.cpp:
#include "foo.h"
int x = 25;
int * f() { return &x; }
Users include foo.h
and need to link in the translation unit containing foo.cpp
in order to call f
. Every such call returns the same address.
2. Separate, TU-local variables
foo.h:
static int x = 35;
static int * f() { return &x; }
Every TU that includes foo.h
gets a separate and distinct function f
, calling which results in a unique value per TU.
3. Baby’s first ODR violation
foo.h:
static int x = 45;
inline int * f() { return &x; }
This appears to be a header-only library, but if foo.h
is included in more than one TU, this constitutes an ODR violation, since f
would be defined more than once but not all of its definitions would be identical.
This is a common mistake. Workarounds include things like making x
a template or replacing x
with a function like int & x() { static int impl = 45; return impl; }
. Note that if you omit the static
, you would most likely get a linker error because of multiple definitions of x
; static
seemingly “makes the code compile”.
4. C++17 to the rescue: Proper header-only libraries
foo.h:
inline int x = 55;
inline int * f() { return &x; }
This version is functionally equivalent to (1), but does not require a dedicated translation unit to contain the definitions of x
and f
.