At the 2016 Oulu ISO C++ Standards meeting, a proposal called Inline Variables was voted into C++17 by the standards committee.
In layman\'s terms, what are inline v
Minimal runnable example
This awesome C++17 feature allow us to:
constexpr
: How to declare constexpr extern?main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
inline constexpr int notmain_i = 42;
const int* notmain_func();
#endif
notmain.cpp
#include "notmain.hpp"
const int* notmain_func() {
return ¬main_i;
}
Compile and run:
g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main
GitHub upstream.
See also: How do inline variables work?
C++ standard on inline variables
The C++ standard guarantees that the addresses will be the same. C++17 N4659 standard draft 10.1.6 "The inline specifier":
6 An inline function or variable with external linkage shall have the same address in all translation units.
cppreference https://en.cppreference.com/w/cpp/language/inline explains that if static
is not given, then it has external linkage.
GCC inline variable implementation
We can observe how it is implemented with:
nm main.o notmain.o
which contains:
main.o:
U _GLOBAL_OFFSET_TABLE_
U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i
notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i
and man nm
says about u
:
"u" The symbol is a unique global symbol. This is a GNU extension to the standard set of ELF symbol bindings. For such a symbol the dynamic linker will make sure that in the entire process there is just one symbol with this name and type in use.
so we see that there is a dedicated ELF extension for this.
Pre-C++ 17: extern const
Before C++ 17, and in C, we can achieve a very similar effect with an extern const
, which will lead to a single memory location being used.
The downsides over inline
are:
constexpr
with this technique, only inline
allows that: How to declare constexpr extern?main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.cpp
#include "notmain.hpp"
const int notmain_i = 42;
const int* notmain_func() {
return ¬main_i;
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
extern const int notmain_i;
const int* notmain_func();
#endif
GitHub upstream.
Pre-C++17 header only alternatives
These are not as good as the extern
solution, but they work and only take up a single memory location:
A constexpr
function, because constexpr implies inline and inline
allows (forces) the definition to appear on every translation unit:
constexpr int shared_inline_constexpr() { return 42; }
and I bet that any decent compiler will inline the call.
You can also use a const
or constexpr
static integer variable as in:
#include <iostream>
struct MyClass {
static constexpr int i = 42;
};
int main() {
std::cout << MyClass::i << std::endl;
// undefined reference to `MyClass::i'
//std::cout << &MyClass::i << std::endl;
}
but you can't do things like taking its address, or else it becomes odr-used, see also: https://en.cppreference.com/w/cpp/language/static "Constant static members" and Defining constexpr static data members
C
In C the situation is the same as C++ pre C++ 17, I've uploaded an example at: What does "static" mean in C?
The only difference is that in C++, const
implies static
for globals, but it does not in C: C++ semantics of `static const` vs `const`
Any way to fully inline it?
TODO: is there any way to fully inline the variable, without using any memory at all?
Much like what the preprocessor does.
This would require somehow:
Related:
Tested in Ubuntu 18.10, GCC 8.2.0.
The first sentence of the proposal:
” The
inline
specifier can be applied to variables as well as to functions.
The ¹guaranteed effect of inline
as applied to a function, is to allow the function to be defined identically, with external linkage, in multiple translation units. For the in-practice that means defining the function in a header, that can be included in multiple translation units. The proposal extends this possibility to variables.
So, in practical terms the (now accepted) proposal allows you to use the inline
keyword to define an external linkage const
namespace scope variable, or any static
class data member, in a header file, so that the multiple definitions that result when that header is included in multiple translation units are OK with the linker – it just chooses one of them.
Up until and including C++14 the internal machinery for this has been there, in order to support static
variables in class templates, but there was no convenient way to use that machinery. One had to resort to tricks like
template< class Dummy >
struct Kath_
{
static std::string const hi;
};
template< class Dummy >
std::string const Kath_<Dummy>::hi = "Zzzzz...";
using Kath = Kath_<void>; // Allows you to write `Kath::hi`.
From C++17 and onwards I believe one can write just
struct Kath
{
static std::string const hi;
};
inline std::string const Kath::hi = "Zzzzz..."; // Simpler!
… in a header file.
The proposal includes the wording
” An inline static data member can be defined in the class definition and may specify a brace-or-equal-initializer. If the member is declared with the
constexpr
specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see D.X). Declarations of other static data members shall not specify a brace-or-equal-initializer
… which allows the above to be further simplified to just
struct Kath
{
static inline std::string const hi = "Zzzzz..."; // Simplest!
};
… as noted by T.C in a comment to this answer.
Also, the constexpr
specifier implies inline
for static data members as well as functions.
Notes:
¹ For a function inline
also has a hinting effect about optimization, that the compiler should prefer to replace calls of this function with direct substitution of the function's machine code. This hinting can be ignored.
Inline variables are very similar to inline functions. It signals the linker that only one instance of the variable should exist, even if the variable is seen in multiple compilation units. The linker needs to ensure that no more copies are created.
Inline variables can be used to define globals in header only libraries. Before C++17, they had to use workarounds (inline functions or template hacks).
For instance, one workaround is to use the Meyer's singleton with an inline function:
inline T& instance()
{
static T global;
return global;
}
There are some drawbacks with this approach, mostly in terms of performance. This overhead could be avoided by template solutions, but it is easy to get them wrong.
With inline variables, you can directly declare it (without getting a multiple definition linker error):
inline T global;
Apart from header only libraries, there other cases where inline variables can help. Nir Friedman covers this topic in his talk at CppCon: What C++ developers should know about globals (and the linker). The part about inline variables and the workarounds starts at 18m9s.
Long story short, if you need to declare global variables that are shared between compilation units, declaring them as inline variables in the header file is straightforward and avoids the problems with pre-C++17 workarounds.
(There are still use cases for the Meyer's singleton, for instance, if you explicitely want to have lazy initialization.)