Mixing extern and const

C++17 inline variables

If you think you want an extern const, then it is more likely that you would actually want to use 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(&notmain_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 &notmain_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.

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

extern const does work as in the example below, but 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(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_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

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:

  • C++11 enum with class members and constexpr link-time optimization

Tested in Ubuntu 18.10, GCC 8.2.0.


  • Yes, you can use them together.
  • And yes, it should exactly match the declaration in the translation unit it's actually declared in. Unless of course you are participating in the Underhanded C Programming Contest :-)

The usual pattern is:

  • file.h:
    extern const int a_global_var;
  • file.c:
    #include "file.h"
    const int a_global_var = /* some const expression */;

Edit: Incorporated legends2k's comment. Thanks.


You can use them together and you can do all sorts of things which ignore the const keyword, because that's all it is; a keyword. It tells the compiler that you won't be changing a variable which in turn allows the compiler to do some useful optomisations and stops you from changing things you didn't mean to.

Possibility.com has a decent article with some more background.


You can use them together. But you need to be consistent on your use of const because when C++ does name decoration, const is included in the type information that is used to decorate the symbol names. so extern const int i will refer to a different variable than extern int i

Unless you use extern "C" {}. C name decoration doesn't pay attention to const.