Reputation: 993
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;
Upvotes: 98
Views: 37206
Reputation: 39588
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:
const
The former part can be done by using const
in the header, latter part is only relevant to the initialization in the source file.
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.
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 declareE
as one of the same type.- [...]
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 asconst
. [...]
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
orconsteval
specifier, then all its declarations shall contain the same specifier.
No, there are only restrictions for functions, not for variables. It is allowed to make one declaration const
and the other constexpr
.
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.
Upvotes: 4
Reputation: 5249
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
Upvotes: 47
Reputation: 59997
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.
Upvotes: 4
Reputation: 382782
C++17 inline
variables
This awesome C++17 feature allow us to:
constexpr
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
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.
Upvotes: 38
Reputation: 1269
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:
Upvotes: 8
Reputation: 1008
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
.
Upvotes: 2
Reputation: 181
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;
}
Upvotes: 1