208

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 variables, how do they work and what are they useful for? How should inline variables be declared, defined and used?

jotik
  • 17,044
  • 13
  • 58
  • 123
  • 1
    @jotik I guess the equivalent operation would be replacing any occurrence of the variable by its value. Normally this is only valid if the variable is `const`. – melpomene Jun 26 '16 at 22:17
  • 19
    That's not the only thing that the `inline` keyword does for functions. The `inline` keyword, when applied to functions, has one other crucial effect, which translates directly to variables. An `inline` function, that's presumably declared in a header file, will not result in "duplicate symbol" errors at link time, even if the header gets `#include`d by multiple translation units. The `inline` keyword, when applied to variables, will have the same exact result. The End. – Sam Varshavchik Jun 26 '16 at 22:22
  • 7
    ^ In the sense of 'substitute any call to this function with an in-place copy of its code', `inline` is only a weak, non-binding request to the optimiser. Compilers are free to not inline requested functions and/or to inline ones you didn't annotate. Rather, the actual purpose of the `inline` keyword is to circumvent multiple definition errors. – underscore_d Jul 25 '16 at 07:46

3 Answers3

188

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. 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 s‌​pecify 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­-in‌​itializer

… 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.
Ofek Shilon
  • 14,734
  • 5
  • 67
  • 101
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 2
    Also, the const restriction only applies to namespace scope variables. Class-scope ones (like `Kath::hi`) don't have to be const. – T.C. Jun 26 '16 at 22:05
  • @T.C: oh, " ​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". – Cheers and hth. - Alf Jun 26 '16 at 22:07
  • 4
    [Newer reports](https://botondballo.wordpress.com/2016/07/06/trip-report-c-standards-meeting-in-oulu-june-2016/) indicate that the `const` restriction is dropped entirely. – T.C. Jul 06 '16 at 19:19
  • @T.C. Thanks. It's too late in the day for me to update, and I may forget. Feel free to take charge. :) – Cheers and hth. - Alf Jul 06 '16 at 19:24
  • `gcc -std=c++17` fails to compile this. do you know any compiler that compile this? – Nick Sep 28 '16 at 14:44
  • 2
    @Nick: Since Richard Smith (the current C++ committee "project editor") is one of the two authors, and since he is "the code owner of the Clang C++ frontend", guessed Clang. And the construct compiled with clang 3.9.0 over at [Godbolt](http://gcc.godbolt.org/#). It warns that inline variables are a C++1z extension. I found no way to share the source and compiler choice and options, so the link is just to the site in general, sorry. – Cheers and hth. - Alf Sep 28 '16 at 17:31
  • 3
    why need for inline keyword inside of class/struct declaration? Why not allowing simply `static std::string const hi = "Zzzzz...";`? – sasha.sochka May 19 '17 at 01:33
  • 1
    Could this be used to create singletons as a simple instance defined in a header file? No more singleton<> base classes and Instance() functions would be needed. – Emilian Cioca Aug 01 '17 at 13:58
  • 3
    @EmilianCioca: No, you'd run afoul of the [static initialization order fiasco](https://isocpp.org/wiki/faq/ctors#static-init-order). A singleton is essentially a device to avoid that. – Cheers and hth. - Alf Aug 01 '17 at 23:39
  • So I noticed that you dropped the template from the C++17 example. If template must be added back there... this doesn't really seem like a simplification :( – Jonathan Mee Jan 24 '18 at 13:28
  • 1
    @Cheersandhth.-Alf [Nir Friedman says](https://youtu.be/xVT1y0xWgww?t=1498) that `inline` variables in a header file will always end up being constructed in the correct order because the header they are defined in must be included before they can be referenced, and therefore any given translation unit will include those variables in the correct, necessary construction order. If header "B.h" depends on a global in "A.h", then "B.h" must `#include` "A.h" itself in order to reference the global, and so by the time "B.h" is included in an actual translation unit, the globals are properly ordered. – monkey0506 Jul 11 '18 at 12:39
  • @monkey0506: The argument for inline variables only (no mix with use of ordinary statics), and where they don't depend on the effects of each other's initialization (e.g. a depends on info in r, which info b installs), seems airtight now that you mention it. So it seems I was wrong about always having the possibility of static initialization order fiasco. Apparently there is this large class of most natural use where one doesn't have that problem. Sorry, and thanks! Disclaimer: I haven't watched the video, saw no transcript, and I think video material for that reason is a little suspect. – Cheers and hth. - Alf Jul 11 '18 at 16:07
  • 1
    @monkey0506 One thing I've since become aware of is that this doesn't apply for template variables (or statics of class templates). That is, there are basically no guarantees on their ordering. So if you are writing code that is simultaneously generic and involves globals you have to be very, very careful. I was reading the standard and thinking how you could try to give at least some reasonable guarantees for these variables, but like most things in C++, it's not simple. – Nir Friedman Oct 17 '18 at 18:39
48

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 Meyers’ 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 Meyers’ singleton, for instance, if you explicitly want to have lazy initialization.)

user3840170
  • 26,597
  • 4
  • 30
  • 62
Philipp Claßen
  • 41,306
  • 31
  • 146
  • 239
  • 2
    Can you elaborate more on performance issues with the Meyer's singleton? – Juraj Oršulić Aug 14 '20 at 19:59
  • 1
    @JurajOršulić For most use cases, the overhead of the Meyer's singletons pattern is not high. Compilers optimize heavily for the fast path (i.e. accessing an already initialized variable). Still, there is overhead, as you need to provide thread-safe code to handle the lazy initialization. For details, the language feature is sometimes called "magic statics" and was introduced in C++11. The N2660 paper contains information on how compilers can implement it efficiently to keep the overhead to a minimum: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm#Appendix – Philipp Claßen Aug 14 '20 at 22:16
  • 2
    Ah, so you were talking about the overhead of making the magic statics initialization thread-safe, as opposed to rolling your own non-thread safe version with templates (how would you go about that, if you weren't using these newly introduced inline globals?). Thanks for elaboration. – Juraj Oršulić Aug 15 '20 at 21:22
  • So I have a namespace with no class. I now want to int variables. I need to be able to update the value of the two int variables at runtime. Using c++20. Can’t work out how to do that. – Andrew Truckle Aug 17 '23 at 03:57
29

Minimal runnable example

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.

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(&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

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.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • 4
    `inline` has almost nothing to do with inlining, neither for functions nor for variables, despite the word itself. `inline` does not tell the compiler to inline anything. It tells the linker to make sure that there is only one definition, which has been traditionally the job of the programmer. So "Any way to fully inline it?" is at least a completely unrelated question. – not-a-user Jun 27 '19 at 21:23
  • 1
    @ not-as-user it's a common myth that inline has nothing to do with inlining. Visual Studio uses it as a hint and will not inline otherwise (unless /LTCG). The converse is not true that with inline it always inlines. In fact sometimes it even does not inline despite the keyword in super simple one line getters. – gast128 Jan 08 '22 at 10:49
  • 1
    I could spend my whole rep bountying answers like this +50 :) – Matthieu Mar 09 '22 at 10:50
  • 1
    @Matthieu thanks for the support! Definitely beware of vote fraud implications though, I think you are aware. Even bounty on a single question with a predefined user in mind is a potential risk. That said, I appreciate the idea of giving one's entire rep away, sand mandalas come to mind: https://en.wikipedia.org/wiki/Sand_mandala It would be cool if there was a query to find the user who has given the most total bounties ever. – Ciro Santilli OurBigBook.com Mar 09 '22 at 11:04