Как объявить constexpr extern?
Можно ли объявить переменную extern constexpr
и определить это в другом файле?
Я пробовал, но компилятор выдает ошибку:
Декларация
constexpr
переменная 'i
это не определение
в.ч:
extern constexpr int i;
в.cpp:
constexpr int i = 10;
7 ответов
Нет, вы не можете этого сделать, вот что говорит стандарт (раздел 7.1.5):
1 Спецификатор constexpr должен применяться только к определению переменной или шаблона переменной, объявлению функции или шаблона функции или объявлению статического члена данных литерального типа (3.9). Если какое-либо объявление функции, шаблона функции или шаблона переменной имеет спецификатор constexpr, то все его объявления должны содержать спецификатор constexpr. [Примечание: явная специализация может отличаться от объявления шаблона в отношении спецификатора constexpr. Параметры функции не могут быть объявлены constexpr. - конец примечания]
некоторые примеры, приведенные в стандарте:
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
C++17 inline
переменные
Эта удивительная особенность C++17 позволяет нам:
- удобно использовать только один адрес памяти для каждой константы
- хранить его как
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;
}
Скомпилируйте и запустите:
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
Стандарт C++ гарантирует, что адреса будут одинаковыми. Проект стандарта C++17 N4659 10.1.6 " Встроенный спецификатор":
6 Встроенная функция или переменная с внешней связью должны иметь одинаковый адрес во всех единицах перевода.
cppreference https://en.cppreference.com/w/cpp/language/inline объясняет, что если static
не дано, то имеет внешнюю связь.
Смотрите также: Как работают встроенные переменные?
Вероятно, вам нужна инициализация extern и constexpr, например:
// in header
extern const int g_n;
// in cpp
constexpr int g_n = 2;
Это поддержка, хотя в Visual Studio 2017 только через режим соответствия:
Нет. Extern constexpr не имеет никакого смысла. Пожалуйста, прочитайте http://en.cppreference.com/w/cpp/language/constexpr
то есть бит "он должен быть немедленно создан или ему присвоено значение".
Сделать это можно, просто нужно заменить на в шапке:
// declaration, possibly in header
extern const int i;
// source file
constexpr int i = 0;
Основная идея объектов заключается в следующем:
- сделай это
- инициализировать его постоянным выражением
Первую часть можно выполнить, используя заголовок, вторая часть относится только к инициализации в исходном файле.
Это действительно разрешено?!
Да. Давайте посмотрим на соответствующие разделы стандарта:
Два объявления сущностей объявляют одну и ту же сущность, если, [...] они соответствуют , имеют одну и ту же целевую область, которая не является областью действия функции или параметра шаблона, и либо
- они появляются в одной единице перевода или [...]
- они оба объявляют имена с внешней связью.
Проще говоря, обаi
имеют одинаковое имя, поэтому они соответствуют, и они оба имеют внешнюю связь из-заextern
.
Для любых двух объявлений сущности:
- Если один объявляется переменной или функцией, другой должен объявить
E
как однотипный.- [...]
Возникает вопрос: являются ли две переменные одного и того же типа, если одна из них , а другая ?
Спецификатор, используемый в объявлении объекта, объявляет объект как . [...]
Ответ — да, он просто создает наш объект и никаким другим образом не меняет тип. Единственный остающийся вопрос заключается в том, разрешено ли нам делать одну декларацию, но нельзя делать другую:
Если какое-либо объявление функции или шаблона функции имеет или
consteval
спецификатор, то все его объявления должны содержать один и тот же спецификатор.
Нет, ограничения есть только для функций, а не для переменных. Разрешается подать одну декларациюconst
и другие .
Это разрешено, но не является ли это совершенно бесполезным?
Не совсем. Один из возможных вариантов использования — если у вас большойconstexpr
таблица поиска, которую вы хотите вычислить во время компиляции, но не хотите помещать эту инициализацию в заголовок, чтобы сократить время компиляции. Если действительно важно вычислить эту таблицу во время компиляции, но не так важно, чтобы определение ее содержимого было видно повсюду (для встраивания и оптимизации), это может помочь.
-
inline constexpr
потребуется одинаковое определение (и инициализация) везде, чтобы не нарушать правило одного определения. В результате мы несем расходы за каждую единицу перевода, включающую наш заголовок. -
static constexpr
еще хуже, потому что каждая единица перевода имеет свою собственную копию этой большой таблицы поиска. -
extern constexpr
прекрасно описывает этот вариант использования.
Примечание: не все компиляторы соответствуют стандарту по умолчанию. Использовать/Zc:externConstexpr
при компиляции с MSVC.
Я согласен с вышесказанным, но есть следствие. Рассматривать:
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;
}
Какие отпечатки:
1: extern = 0x1000020a8, constexpr = 0x100001ed0
2: extern = 0x1000020a8, constexpr = 0x100001ed4
IE constexpr
выделяется дважды, тогда как extern
выделяется один раз. Это противоречит мне, так как я ожидаю constexpr
быть более оптимизированным, чем extern
,
Редактировать: const
а также constexpr
имеют одинаковое поведение в отношении распределения, поэтому с этой точки зрения поведение является ожидаемым. Хотя, как я уже сказал, я был удивлен, когда наткнулся на поведение constexpr
,
Да, это несколько...
//===================================================================
// 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;
}