Правильно ли говорить, что компилятор может заменить выражение "a-> i" ниже на его значение 1, потому что...?
Код, приведенный ниже, компилируется в GCC, clang и VS2017, а выражение a->i
в операторе return
заменяется его постоянным значением 1. Правильно ли это сказать, что это верно, поскольку a
не является odr-используемым в выражении a->i
?.
struct A
{
static const int i = 1;
};
int f()
{
A *a = nullptr;
return a->i;
}
PS: Я считаю, что a
не является odr-используемым в выражении a->i
потому что он удовлетворяет условию "если" в [basic.def.odr]/4 следующим образом:
Переменная x
, имя которой отображается как потенциально вычисленное выражение ex
является odr, используемое ex
если применение преобразования lvalue-to-rvalue (7.1) к x
дает постоянного выражения (8.6), которое не вызывает никаких нетривиальных функций и, если x
является объектом, ex
является элементом множества потенциальных результатов выражения e
, где либо lvalue-rvalue conversion (7.1) применяется к e
, либо e
является выражением с отбрасыванием (8.2),
В частности, выражение ex == a
является элементом множества потенциальных результатов выражения e == a->i
, согласно [basic.def.odr]/2 (2.3), содержащему выражение ex
, где преобразование lvalue-rvalue применяется к e
.
Ответы
Ответ 1
a
используется odr, потому что вы не выполняете первую часть "если":
применение преобразования lvalue-to-rvalue (7.1) к x
дает постоянное выражение (8.6), которое не вызывает никаких нетривиальных функций
Применение преобразования lvalue-to-r в a
не дает постоянного выражения.
Остальные основные проблемы 315 и 232.
Ваш анализ разбит двумя дополнительными способами:
- "Объектное выражение" определяется с помощью
.
форма доступа к члену класса, поэтому вам необходимо переписать a->i
в точку, т.е. (*a).i
, перед применением [basic.def.odr]/2.3. a
не является членом множества потенциальных результатов этого выражения. - Сама эта пуля является дефектной, поскольку она была написана с учетом нестатических данных. Для статических членов данных набор потенциальных результатов должен быть фактически названным статическим элементом данных - см. Основную проблему 2353, поэтому
a
вдвойне не является членом множества потенциальных результатов этого выражения.
[Expr.const]/2.7:
Выражение e
является выражением основной константы, если оценка e
, следуя правилам абстрактной машины, не будет оценивать одно из следующих выражений:
- [...]
- преобразование lvalue-to-rvalue, если оно не применяется к
- нестабильное значение целого числа или типа перечисления, которое относится к полному энергонезависимому объекту const с предшествующей инициализацией, инициализированным постоянным выражением или
- нестабильное значение glvalue, которое ссылается на подобъект строкового литерала или
- нестабильное значение glvalue, которое относится к энергонезависимому объекту, определенному с помощью
constexpr
, или относится к не изменяемому подобъекту такого объекта, или - нестабильное значение gl буквального типа, которое относится к энергонезависимому объекту, срок жизни которого начинается с оценки
e
;
- [...]
Ответ 2
i
является static
членом класса... поскольку вы можете обращаться к static
членам класса с помощью обычных методов для экземпляров, они не привязаны ни к какому-либо экземпляру, так что вам не нужно разыменовывать nullptr
указатель (как при использовании оператора sizeof
). Вы также можете получить доступ к полю, используя простой
return A::i;
потому что вам не нужно создавать экземпляр для доступа к нему. Действительно, будучи const
, компилятор позволяет управлять им как постоянное значение, поэтому только в том случае, когда вам нужно использовать его адрес (с помощью оператора &
), компилятор может обойти его выделение в постоянной памяти.
Следующий образец будет определять, что:
#include <iostream>
struct A {
static const int i = 1;
};
int main()
{
std::cout << ((A*)0)->i << std::endl;
std::cout << A::i << std::endl;
}
распечатает
$ a.out
1
1
$ _