почему динамическая c инициализация происходит до инициализации stati c в gcc - PullRequest
1 голос
/ 29 мая 2020
#include <iostream>
struct NonConstant{
    NonConstant(int v):v_(v){
        std::cout<<"NonConstant\n";
    }
    int v_;
};

struct Constant{
    constexpr Constant(int v):v_(v){
        if(v_==0){
         std::cout<<"Constant\n";
        }
    }
    int v_;
};

NonConstant a = 2; //#1
Constant b = 0;   //#2

int main(){
}

Результат будет:

NonConstant
Constant

Я запутался в этом исходе, Becuase, согласно стандартным правилам, #1 не является stati c инициализация, #2 из-за них:

Постоянный инициализатор для переменной или временного объекта o - это инициализатор, полное выражение которого является константным выражением, за исключением того, что если o - объект , такой инициализатор может также вызывать конструкторы constexpr для o и его подобъектов, даже если эти объекты относятся к нелитеральным типам классов.
Постоянная инициализация выполняется, если переменная или временный объект со статусом c или продолжительность хранения потока инициализируется константным инициализатором для объекта. Если постоянная инициализация не выполняется, переменная с длительностью хранения stati c или длительностью хранения потока инициализируется нулем. Вместе инициализация нулем и постоянная инициализация называются stati c initialization ; все остальные инициализации являются динамическими c инициализацией. Все stati c инициализация строго выполняется перед ([intro.races]) любой динамикой c инициализацией .

Конструктор класса NonConstant не указан constexpr, инициализация NonConstant a = 2; вызовет конструктор, не являющийся constexpr, для объекта a, следовательно, инициализация для #1 будет не stati c инициализация, так что это динамическая c инициализация. Напротив, инициализация Constant b = 0; является инициализацией c stati, поскольку вызываемый конструктор является конструктором constexpr. И правила говорят, что инициализация всех стати c строго выполняется перед любой динамикой c инициализацией . Итак, почему результат подразумевает, что оценка #1 произошла раньше, чем #2? Если я что-то упустил, поправьте меня.

UPDATE:

В следующих комментариях к этому вопросу кто-то говорит, что, за исключением того, что класс конструктора может быть нелитеральным типом, конструктор constexpr во всех отношениях должен быть допустимым выражением константы ядра , то есть вызов std::cout сделает конструктор constexpr не основным постоянным выражением. Однако я нашел другую интерпретацию в cppreference , а именно:

Постоянная инициализация выполняется после (до C ++ 14) вместо (начиная с C ++ 14) нуля инициализация статических c и локальных объектов потока и перед всеми остальными инициализациями. Инициализируются только следующие переменные:

  1. [...]
  2. Stati c или локальный объект потока типа класса, который инициализируется вызовом конструктора , если конструктором является constexpr и все аргументы конструктора (включая неявные преобразования) являются постоянными выражениями, и если инициализаторы в списке инициализаторов конструктора и инициализаторы фигурных скобок или равных членов класса содержат только константные выражения .

Это не говорит о том, что конструктор constexpr должен быть выражением константы ядра. Пока вызываемый конструктор удовлетворяет требованиям constexpr и все его аргументы должны быть постоянными выражениями, а инициализатор-член должен быть постоянными выражениями. Итак, #2 действительно является постоянной инициализацией, потому что аргумент 0 является постоянным выражением, а выбранный конструктор, квалифицированный спецификатором constexpr, и инициализатор члена подчиняются этим правилам, упомянутым в expr.const .

1 Ответ

1 голос
/ 30 мая 2020

b имеет динамическую c инициализацию, а не стат c инициализацию.

Как уже объяснялось в вашей цитате [basi c .start.static] / 2 , b имеет статическую c инициализацию, только если полное выражение его инициализатора, которое является выполнением конструктора Constant(int), является постоянным выражением.

In [expr. const] / 2 , мы читаем:

Выражение e - это выражение основной константы , если не выполняется оценка e, следуя правилам абстрактного машина, будет оценивать одно из следующих выражений:

  • ...

  • вызов функции, отличной от конструктора constexpr для литерала класс, функция constexpr или неявный вызов тривиального деструктора ([class.dtor]) [ Примечание: Разрешение перегрузки применяется как обычно - конец примечания ];

  • ...

Здесь оценка constr uctor, "следуя правилам абстрактной машины", включает тело конструктора. И поскольку инициализатор - 0, эта оценка вызовет std::operator<<(std::ostream&, const char*), а не constexpr. Таким образом, полное выражение инициализатора не является основным постоянным выражением и не является постоянным выражением.

И, конечно, хотя это не строго техническое определение, весь смысл «постоянного выражения» состоит в том, чтобы определить, когда мы ' Гарантируется, что компилятор может иметь дело с чем-то во время компиляции. И запись в стандартный вывод программы точно не произойдет во время компиляции.

cppreference.com - хороший ресурс, который пытается быть максимально точным, но он не заменяет авторитет фактического стандарта. . Эта цитата о постоянной инициализации с использованием конструктора класса неверна для C ++ 14 и C ++ 17. Я подозреваю, что это на самом деле осталось от C ++ 11, в котором телу конструктора constexpr вообще не разрешалось оценивать какие-либо вызовы функций, а [expr.const] аналогичным образом описал требования к использованию конструкторов constexpr внутри основное константное выражение в терминах инициализаторов членов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...