Я пытаюсь понять взаимосвязь между компиляцией, связыванием и определением значений constexpr в C++. Я знаю, что значения constexpr должны быть полностью определены во время компиляции, но некоторые обсуждения предполагают, что они определяются в течение всего процесса трансляции, который включает как компиляцию, так и связывание. Например, рассмотрим следующий отрывок из книги «Эффективный современный C++», который меня смутил:
«Концептуально, constexpr указывает на значение, которое не только постоянное, но и но также известен во время компиляции. Технически их значения определяется в ходе перевода, а перевод состоит не только из компиляция, но и связывание. Если вы не пишете компиляторы или компоновщики однако для C++ это на вас не повлияет, так что вы можете беспечно программе, как если бы значения объектов constexpr были определены во время компиляция».
В моем случае я написал следующий код в source.cpp:
#include <string_view>
constexpr std::string_view c = "Hello world";
constexpr std::string_view func()
{
return c;
}
В main.cpp я попытался использовать упреждающие объявления для этих значений constexpr, но столкнулся с проблемами, которые помешали успешной компиляции.
#include <array>
#include <iostream>
#include <string_view>
constexpr std::string_view func();
int main()
{
constexpr std::string_view s = func();
constexpr size_t l = s.size();
std::array<int, l> arr;
std::cout << arr.size() << '\n';
}
Теперь я в замешательстве из-за того, что написано в книге, и, похоже, это противоречит этому примеру и следующим ошибкам:
main.cpp:10:22: Constexpr variable 'l' must be initialized by a
constant expression main.cpp:10:28: initializer of 's' is not a
constant expression main.cpp:9:33: declared here
но когда я помещаю весь код в main.cpp, он компилируется.
Стандарт C++ не делает различия между «компиляцией» и «связыванием» (фактически слова «компилятор» и «компоновщик» вообще не используются в нормативном тексте — в лучшем случае они используются в примечаниях или сносках). Нормативный текст в стандарте просто относится к этапам перевода (некоторые из которых могут быть реализованы тем, что вы бы назвали «компилятором» или «компоновщиком») и ничего не говорит о том, какие этапы перевода участвуют в оценке constexpr
ценности. Таким образом, любые ответы на ваш вопрос будут в контексте конкретной цепочки инструментов.
Возможно, автор имеет в виду тот факт, что extern int x; constexpr int *p = &x;
действителен, хотя (в типичной реализации) адрес x
не определен до связывания.
Способ определения константных выражений и constexpr
в стандарте проработан тщательно, поэтому компилятор должен иметь возможность выполнять константную оценку только на этапе компиляции.
Компоновщику не обязательно знать об этом, и постоянная оценка не может пересекать единицы трансляции. Я не знаю, что пытается сказать цитата из книги, за исключением того, что, возможно, это (неинформативная) формальность стандарта, формально определяющего все в терминах общего «перевода» вместо отдельной «компиляции» и «связывания». этапы.
В частности, даже если функция объявлена constexpr
, ее все равно можно использовать как часть вычисления константы только в том случае, если она определена (в той же единице перевода) перед константным выражением, в котором она используется. Это объясняет, почему ваш пример пытается инициализировать s
вызовом func
с ошибкой в единице перевода, в которой отсутствует определение func
.
Кроме того, даже если вы не объявили s
и другие переменные как constexpr
(что приводит к тому, что инициализация должна быть постоянным выражением или в противном случае компиляция не удастся), программа все равно будет IFNDR (неверно сформированная, диагностика не требуется), потому что constexpr
на функции подразумевает inline
, а функция inline
должна быть определена (где-то) в каждой единице перевода, в которой она (odr-) используется.
Более того, программа все равно будет IFNDR, даже если вы добавите определение func
к единице перевода main
, поскольку переменные const
(подразумеваемые constexpr
) по умолчанию в C++ имеют внутреннюю связь. Таким образом, два определения func
в двух единицах перевода будут относиться к разным объектам c
, что делает эти два определения несовместимыми для целей ODR.
Константы Constexpr, которые «разделяются» между источниками, должны быть помещены в файл заголовка
inline constexpr std::string_view c{"Hello world!"};
, и этот файл заголовка должен быть включен в источники, которым нужна строка. И в основном вы должны использоватьstatic constexpr std::string_view s{func()};