How to declare constexpr extern?
Asked Answered
D

7

93

Is it possible to declare a variable extern constexpr and define it in another file?

I tried it but the compiler gives error:

Declaration of constexpr variable 'i' is not a definition

in .h:

extern constexpr int i;

in .cpp:

constexpr int i = 10; 
Diarmid answered 13/5, 2015 at 7:45 Comment(0)
L
45

No, you can't do it. Here's what the standard says (section 7.1.5):

1 The constexpr specifier shall be applied only to the definition of a variable or variable template, the declaration of a function or function template, or the declaration of a static data member of a literal type (3.9). If any declaration of a function, function template, or variable template has a constexpr specifier, then all its declarations shall contain the constexpr specifier. [Note: An explicit specialization can differ from the template declaration with respect to the constexpr specifier. Function parameters cannot be declared constexpr. — end note ]

Some examples given by the standard:

constexpr void square(int &x);  // OK: declaration
constexpr int bufsz = 1024;  // OK: definition
constexpr struct pixel {  // error: pixel is a type
    int x;
    int y;
    constexpr pixel(int);  // OK: declaration
};

extern constexpr int memsz; // error: not a definition
Latty answered 13/5, 2015 at 7:56 Comment(0)
P
35

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
  • 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.

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.

See also: How do inline variables work?

Tested in GCC 7.4.0, Ubuntu 18.04.

Propend answered 22/12, 2018 at 15:8 Comment(2)
this doesn't answer the question though. you definitely define it in the header.Eucken
Helpful, exactly what I was looking forMelanite
L
7

What you probably want is extern and constexpr initialization, e.g.:

// in header
extern const int g_n;

// in cpp
constexpr int g_n = 2;

This is support though in Visual Studio 2017 only through conformance mode:

Lindblad answered 13/11, 2018 at 9:35 Comment(1)
In my case this worked with g++ -std=c++14. But the linker failed when compiling with g++ -std=c++17!Buck
B
4

No. Extern constexpr does not make any sense. Please read http://en.cppreference.com/w/cpp/language/constexpr

i.e. the bit

it must be immediately constructed or assigned a value.

Brazilin answered 13/5, 2015 at 7:48 Comment(4)
Okay, "extern constexpr" doesn't make sense. Got it. But can you do a declaration of a constexpr variable? And if so how? Not being able to declare a constexpr variable seems like a huge limitation so I hope that there is some way.Liponis
Actually, extern constexpr does make sense, and would be useful. In particular, static constexpr class member variables automatically have external linkage (which is a huge inconsistency and gotcha - people often forget to add a definition in a .cc file). Fortunately C++17 fixes much of this with inline variables - "inline constexpr" will avoid many of the problems with constexpr globals in headers. Sadly many of us can't use C++17 yet.Dapplegray
@Dapplegray awesome! I've added a full example at: stackoverflow.com/questions/30208685/…Propend
Hmm... I would say extern constexpr makes sense in situations where a compile-time constant may potentially be ODR-used, as a way to tell the compiler that there actually is only a single definition, instead of the usual "if a constant's not ODR-used, act as if there's only a single definition." Not all that common, but you never know when you might need it for something.Paredes
S
3

You can do it, you just need to replace constexpr with const in the header:

// declaration, possibly in header
extern const int i;

// source file
constexpr int i = 0;

The basic idea behind constexpr for objects is:

  • make it const
  • initialize it with a constant expression

The former part can be done by using const in the header, latter part is only relevant to the initialization in the source file.

Is this really allowed?!

Yes. Let's look at the relevant sections in the standard:

Two declarations of entities declare the same entity if, [...], they correspond, have the same target scope that is not a function or template parameter scope, and either

  • they appear in the same translation unit, or [...]
  • they both declare names with external linkage.

- [basic.link] §8

In plain terms, both i have the same name so they correspond, and they both have external linkage due to extern.

For any two declarations of an entity E:

  • If one declares E to be a variable or function, the other shall declare E as one of the same type.
  • [...]

- [basic.link] §11

That begs the question: are two variables the same type if one is constexpr and the other is const?

A constexpr specifier used in an object declaration declares the object as const. [...]

- [dcl.constexpr] §6

The answer is yes, it just makes our object const, it doesn't change the type in any other way. The only remaining question is whether we are allowed to put constexpr on one declaration but not on another:

If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the same specifier.

- [dcl.constexpr] §1

No, there are only restrictions for functions, not for variables. It is allowed to make one declaration const and the other constexpr.

It is allowed, but isn't it totally useless?

Not entirely. One possible use-case is if you have a large constexpr lookup table that you want to compute at compile time, but don't want to put this initialization into a header, in order to reduce compile times. If it's really important to compute this table at compile time, but not so important to have a definition of its contents visible everywhere (for inlining and optimizations), extern constexpr can help.

  • inline constexpr would require an identical definition (and initialization) everywhere to not violate the one definition rule. As a result, we incur the cost in every translation unit that includes our header.
  • static constexpr is even worse, because every translation unit has its own copy of this large lookup table.
  • extern constexpr covers this use-case perfectly.

Note: not all compilers conform to the standard by default. Use /Zc:externConstexpr when compiling with MSVC.

Swiftlet answered 16/6, 2023 at 0:3 Comment(0)
O
2

I agree with 'swang' above, but there is a consequence. Consider:

ExternHeader.hpp

extern int e; // Must be extern and defined in .cpp otherwise it is a duplicate symbol.

ExternHeader.cpp

#include "ExternHeader.hpp"
int e = 0;

ConstexprHeader.hpp

int constexpr c = 0; // Must be defined in header since constexpr must be initialized.

Include1.hpp

void print1();

Include1.cpp

#include "Include1.hpp"
#include "ExternHeader.hpp"
#include "ConstexprHeader.hpp"
#include <iostream>

void print1() {
    std::cout << "1: extern = " << &e << ", constexpr = " << &c << "\n";
}

Include2.hpp

void print2();

Include2.cpp

#include "Include2.hpp"
#include "ExternHeader.hpp"
#include "ConstexprHeader.hpp"
#include <iostream>

void print2() {
    std::cout << "2: extern = " << &e << ", constexpr = " << &c << "\n";
}

main.cpp

#include <iostream>
#include "Include1.hpp"
#include "Include2.hpp"

int main(int argc, const char * argv[]) {
    print1();
    print2();
    return 0;
}

Which prints:

1: extern = 0x1000020a8, constexpr = 0x100001ed0
2: extern = 0x1000020a8, constexpr = 0x100001ed4

IE the constexpr is allocated twice whereas the extern is allocated once. This is counterintuitive to me, since I 'expect' constexpr to be more optimized than extern.

Edit: const and constexpr have the same behaviour, with regard to allocation, therefore from that point of view the behaviour is as expected. Though, as I said, I was surprised when I came across the behaviour of constexpr.

Occiput answered 2/12, 2016 at 4:55 Comment(2)
It's an optimization thing - the constexpr is placed at local scope (whenever needed) to have fast access, extern is placed in the global data segment to allow reads/writes to the same variable (it's probbaly done through a pointer)Fireback
@Fireback constexpr doesn't relocate variables into a different scope. You're probably thinking of constant folding, which is a compiler optimization that might be performed. We see two addresses for constexpr not because it's put into some local scope, but because const objects have internal linkage, so every source file has its own copy. inline constexpr fixes this.Swiftlet
C
1

Yes it somewhat is...

//===================================================================
// afile.h

#ifndef AFILE
#define AFILE

#include <cstddef>
#include <iostream>

enum class IDs {

  id1,
  id2,
  id3,
  END

};

// This is the extern declaration of a **constexpr**, use simply **const**
extern const int ids[std::size_t(IDs::END)];

// These functions will demonstrate its usage

template<int id> void Foo() { std::cout << "I am " << id << std::endl; }

extern void Bar();

#endif // AFILE

//===================================================================
// afile.cpp

#include "afile.h"

// Here we define the consexpr. 
// It is **constexpr** in this unit and **const** in all other units
constexpr int ids[std::size_t(IDs::END)] = {

  int(IDs::id1),
  int(IDs::id2),
  int(IDs::id3)

};

// The Bar function demonstrates that ids is really constexpr
void Bar() {

  Foo<ids[0]      >();
  Foo<ids[1] + 123>();
  Foo<ids[2] / 2  >();

}

//===================================================================
// bfile.h

#ifndef BFILE
#define BFILE

// These functions will demonstrate usage of constexpr ids in an extern unit

extern void Baz();
extern void Qux();


#endif // BFILE

//===================================================================
// bfile.cpp

#include "afile.h"

// Baz demonstrates that ids is (or works as) an extern field
void Baz() {

  for (int i: ids) std::cout << i << ", ";
  std::cout << std::endl;

}

// Qux demonstrates that extern ids cannot work as constexpr, though
void Qux() {

#if 0 // changing me to non-0 gives you a compile-time error...

  Foo<ids[0]>();

#endif

  std::cout << "Qux: 'I don't see ids as consexpr, indeed.'" 
            << std::endl;

}

//===================================================================
// main.cpp

#include "afile.h"
#include "bfile.h"

int main(int , char **)
{

  Bar();
  Baz();
  Qux();

  return 0;
}
Chellman answered 10/8, 2016 at 9:18 Comment(2)
I successfully used the trick extern const + constexpr to allocate a complex structure in .text segment for an embedded project with GCC 5.4.1.Miosis
Is it even legal?Hosfmann

© 2022 - 2025 — McMap. All rights reserved.