82

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; 
Ilya
  • 4,460
  • 4
  • 24
  • 46
coldbrew
  • 833
  • 1
  • 6
  • 5

6 Answers6

41

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
swang
  • 4,969
  • 5
  • 31
  • 53
29

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.

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.

Matthieu
  • 2,816
  • 4
  • 57
  • 83
Ed Heal
  • 57,599
  • 16
  • 82
  • 120
  • 3
    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. – Bruce Dawson Aug 19 '16 at 21:36
  • 23
    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. – SethML Apr 20 '18 at 18:50
  • @SethML awesome! I've added a full example at: https://stackoverflow.com/questions/30208685/how-to-declare-constexpr-extern/53896773#53896773 – Ciro Santilli Путлер Капут 六四事 Dec 22 '18 at 15:10
  • 1
    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. – Justin Time - Reinstate Monica Jul 23 '19 at 17:38
4

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:

gast128
  • 1,093
  • 11
  • 21
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.

Howard Lovatt
  • 950
  • 8
  • 15
  • 3
    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) – Xeverous Jan 31 '17 at 18:32
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;
}
PavDub
  • 113
  • 1
  • 5