Правильно ли говорить, что компилятор может заменить выражение "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
$ _