C++17 inline
variables
If you Googled "C++ const static", then this is very likely what you really want to use are C++17 inline variables.
This awesome C++17 feature allow us to:
- conveniently use just a single memory address for each constant
- store it as a
constexpr
: How to declare constexpr extern?
- do it in a single line from one header
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:
- it is not possible to make the variable
constexpr
with this technique, only inline
allows that: How to declare constexpr extern?
- it is less elegant as you have to declare and define the variable separately in the header and cpp file
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 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: 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:
- forbidding or detecting if the address of the variable is taken
- add that information to the ELF object files, and let LTO optimize it up
Related:
Tested in Ubuntu 18.10, GCC 8.2.0.