Недавно я был удивлен, что следующий код также компилируется в clang, gcc и msvc (по крайней мере, в их текущих версиях).
struct A {
static const int value = 42;
};
constexpr int f(A a) { return a.value; }
void g() {
A a; // Intentionally non-constexpr.
constexpr int kInt = f(a);
}
Насколько я понял, вызов f
не является constexpr, потому что аргумент i
не является таковым, но, похоже, я ошибаюсь. Это правильный код, поддерживаемый стандартом, или какое-то расширение компилятора?
f
и i
не нарушают эти правила... ничто не остановит постоянную оценку.
@MarcusMüller Потому что он не был объявлен как constexpr
. Например, int x = 5; constexpr int y = x;
не будет компилироваться, даже если компилятор знает значение x
.
но это является константа времени компиляции! То, что вы не называете это constexpr
, этого не меняет.
Насколько я знаю, он будет работать даже с переменной, но для нее будет сгенерирована стандартная функция времени выполнения.
Как упоминалось в комментариях, правила для константных выражений обычно не требуют, чтобы каждая переменная, упомянутая в выражении и время жизни которой началось вне оценки выражения, была constexpr
.
Существует (длинный) список требований, невыполнение которых не позволяет выражению быть постоянным выражением. Пока ни одно из них не нарушено, выражение является константным выражением.
Требование, чтобы используемая переменная/объект была constexpr
, формально известно как объект, являющийся можно использовать в постоянных выражениях (хотя точное определение содержит более подробные требования и исключения, см. также связанную страницу cppreference).
Глядя на список видно, что это свойство требуется только в определенных ситуациях, а именно только для переменных/объектов, время жизни которых началось вне выражения и если над ним либо выполняется вызов виртуальной функции, либо выполняется преобразование lvalue-to-rvalue на нем или это ссылочная переменная, названная в выражении.
Ни один из этих случаев здесь не применим. Виртуальные функции не задействованы, а a
не является ссылочной переменной. Обычно преобразование lvalue в rvalue делает требование важным. Преобразование lvalue-to-rvalue происходит всякий раз, когда вы пытаетесь использовать значение, хранящееся в объекте или одном из его подобъектов. Однако A
— это пустой класс без какого-либо состояния, и поэтому нет никакого значения для чтения. При передаче a
в функцию вызывается неявный конструктор копирования для создания параметра f
, но, поскольку класс пуст, он фактически ничего не делает. Он не имеет доступа ни к какому состоянию a
.
Обратите внимание, что, как упоминалось выше, правила более строгие, если вы используете ссылки, например.
A a;
A& ar = a;
constexpr int kInt = f(ar);
не удастся, потому что ar
называет ссылочную переменную, которую нельзя использовать в константных выражениях. Мы надеемся, что это будет исправлено в ближайшее время, чтобы быть более последовательным. (см. https://github.com/cplusplus/papers/issues/973)
Ясно, в общем, моя интуиция заключалась в том, что вызов функции вызовет преобразование lvalue в rvalue, но этого не произошло, потому что тип был пустым. Действительно, добавление нестатического члена в A
приводит к сбою компиляции примера, но он все еще работает, когда нет вызова функции, то есть constexpr int kInt = a.value;
. Теперь будет легче полагаться на эту функцию. Спасибо за объяснение!
@dtldarek Да, и если вы позволите функции принимать свой аргумент по ссылке, то она будет работать, даже если вы добавите нестатические члены, поскольку единственное преобразование объекта из lvalue в rvalue происходит в конструкторе копирования, который мы можно избежать таким образом.
как может
i
не быть константой времени компиляции?