Гарантирован ли порядок инициализации шаблонных переменных constexpr (то есть, инициализированных константой)? - PullRequest
0 голосов
/ 18 мая 2018

С ru.cppreference.com / w / cpp / language / initialization :

Неупорядоченная динамическая инициализация, которая [sic] применяется только к (статическим / локальным для потока) членам статических данных шаблонов классов и шаблонам переменных (начиная с C ++ 14), которые не являются явно специализированными.

Следовательно, статические шаблоны кажутся уязвимыми для еще худшей версии Статический порядок инициализации Fiasco (TSIOF) (т. Е. Неупорядоченный в единице перевода).

Устраняет ли использование constexpr эту уязвимость?

т.е. гарантированно будет ли вывод приведенного ниже кода равным success?

Очевидно, что из-за характера этого вопроса рабочие примеры не будут достаточными в качестве ответов; цитаты из стандарта потребуются. (Предпочтительно ответы на C ++ 17)

#include<cassert>

template<class T> static constexpr T a = 41;
template<class T> static constexpr T b = a<T>+1;
int main(){
    assert(b<int> == 42);
    std::cout <<"success\n";
}

Кстати, если кто-то является экспертом в этом вопросе, у меня есть связанный, без ответа вопрос (такому эксперту было бы легко ответить) здесь . Кроме того, каковы будут последствия, если ответ на мой другой вопрос будет отрицательным (то есть constexpr не помогает в разных единицах перевода)?

ОБНОВЛЕНИЕ: мне нужно уточнить, что меня беспокоит. В оригинальном заголовке вопроса спрашивалось, имеет ли значение порядок инициализации для переменных шаблона constexpr. Я уточнил это. Меня не волнует, происходит ли динамическая инициализация в примере; это не так Меня беспокоит то, что поскольку упорядоченную инициализацию нельзя предполагать в случае динамической инициализации, можно ли ее предполагать в случае постоянной инициализации? До того, как увидеть поведение динамически инициализируемых шаблонных переменных (в пределах одной и той же единицы перевода), я бы никогда не подумал об этом. Однако, поскольку динамически инициализируемые переменные шаблона статической длительности не обеспечивают упорядоченную инициализацию, я теперь не вижу причин предполагать, что переменные шаблона статической длительности с постоянной инициализацией также гарантировали упорядоченную инициализацию. Я должен быть на 100% уверен, что постоянная инициализация шаблонных переменных происходит в порядке их определения в TU.

Опять же, я не вижу причин полагать, что для инициализации константы-инициализатора в компиляторе требуется порядок, если динамический инициализатор - нет. Отсутствие в стандарте предупреждения о неупорядоченной постоянной инициализации не является достаточным.

Я понимаю, что некоторые могут подумать, что это чрезмерное беспокойство, но я работаю над критически важным для безопасности программным обеспечением, и моя компания отложила принятие C ++ 14 до тех пор, пока эта проблема не будет решена.

Ответы [ 2 ]

0 голосов
/ 19 июля 2019

[Обратите внимание, что в этом посте a<T> и b<T> сокращены до a и b соответственно.]

[expr.const/3] утверждает, что:

Переменная может использоваться в константных выражениях после того, как встречается ее инициализирующее объявление, если это переменная constexpr, или она имеет ссылочный тип или const-квалифицированный целочисленный тип или тип перечисления, а его инициализатор является инициализатором констант.

Таким образом, a будет использоваться во время инициализации b из-за спецификатора constexpr.constexpr полностью применимо как к переменным, так и к шаблонам переменных, как указано в первом предложении [dcl.constexpr/1].

Спецификатор constexpr должен применяться только копределение переменной или шаблона переменной или объявление функции или шаблона функции.

Таким образом, переменная или шаблон переменной, объявленный constexpr, может использоваться в константных выражениях (включая инициализацию других * 1035)* переменные) после его собственной инициализации со значением, указанным в его инициализации (из-за постоянной инициализации, косвенно гарантируемой constexpr).


Если мы хотим исследовать второй вариант в [expr.const/3]вместо того, чтобы полагаться на условие «constexpr означает« да »», это также приведет к тому, что a будет использоваться во время инициализации b, хотя и по более сложному маршруту, который включает в себя углубление во многие части стандарта и разбиение на части.вместе неявные гарантии.

Во-первых, возник бы вопрос о том,a имеет постоянный инициализатор .Чтобы определить, имеет ли он постоянный инициализатор, мы можем обратиться к [expr.const/2], который гласит (примечание пропущено):

A константа инициализатора для переменнойили временный объект o является инициализатором, для которого интерпретация его полного выражения как константа-выражения приводит к константному выражению, за исключением того, что если o является объектом, такой инициализатор может также вызывать конструкторы constexpr для o иего подобъекты, даже если эти объекты имеют не-литеральные типы классов.

У нас есть две явные гарантии того, что полное выражение инициализатора является константным выражением, оба из [dcl.constexpr/9]: a неявно const, а полное выражение , а не , являющееся константным выражением, будет ошибкой.

Спецификатор constexpr, используемый в объявлении объектаобъявляет объект как const.Такой объект должен иметь буквальный тип и должен быть инициализирован.В любом объявлении переменной constexpr полное выражение инициализации должно быть константным выражением.

Это также означает, что (начиная с C ++ 14), a не будет инициализироваться нулем ([basic.start.static/2], нерелевантные части опущены):

Инициализация констант выполняется, если переменнаяили временный объект со статическим или потоковым сроком хранения инициализируется константным инициализатором ([expr.const]) для объекта.Если постоянная инициализация не выполняется, переменная со статической продолжительностью хранения ([basic.stc.static]) или продолжительностью хранения потока ([basic.stc.thread]) инициализируется нулями ([dcl.init]).

Чтобы убедиться, что a имеет правильное значение после инициализации, если мы хотим быть действительно тщательными, мы могли бы тогда взглянуть на [intro.execution/9]:

Каждое вычисление значения и побочный эффект, связанный с полным выражением, секвенируются перед каждым вычислением значения и побочным эффектом, связанным со следующим полным выражением, которое будет оценено.

Поскольку мы знаем, что инициализация a является полным выражением, мы неявно гарантируем, что a будет присвоено значение 41 в конце его полного выражения, до следующего полного Выражение (включая другие инициализации) в последовательности оценивается. Объединив это с [expr.const/3] и [basic.start.static/2], мы, таким образом, гарантируем, что (как и в случае с constexpr), a можно использовать в константных выражениях, встречающихся после его собственной инициализации, такой как инициализация b, а также гарантировано, что a == 41 && a != 0.

0 голосов
/ 18 мая 2018

На основе basic.start.static :

Инициализация константы выполняется, если переменная или временная объект с статическим или продолжительностью хранения потока инициализируется постоянный инициализатор для сущности.

В вашем коде:

template<class T> static constexpr T a = 41; // constant initialization

выполняет постоянную инициализацию , что составляет:

template<class T> static constexpr T b = a<T>+1;

инициализируется с 42 из-за шаблонов постоянная оценка .

, в котором говорится, что (от expr.const / 8.7 ):

переменная, имя которой отображается как потенциально постоянная выражение, которое является либо constexpr переменной , либо является энергонезависимым квалифицированный константный целочисленный тип или ссылочный тип.

Таким образом, гарантируется, что выход всегда будет "success" ful.

ПРИМЕЧАНИЕ С basic.start.static / 2 :

Вместе нулевая инициализация и постоянная инициализация называются статическая инициализация

- не динамическая инициализация

...