Постоянная инициализация зависимых нелокальных постоянных переменных с плавающей запятой со статической продолжительностью хранения в различных единицах трансляции

Мне интересно, могу ли я полагаться на постоянную инициализацию, когда есть зависимость между двумя постоянными нелокальными переменными с плавающей запятой со статической продолжительностью хранения в двух разных единицах перевода - где одна зависит от (инициализируется на [значение]) другой , а для последнего выполняется постоянная инициализация. Я ищу ответы, содержащие и интерпретирующие соответствующую часть стандарта, в частности, стандарт C ++ 11.

// Note: the non-use of constexpr is intended (C++03 compatibility)

// foo.h
struct Foo {
  static const float kValue;
};

// foo.cpp
const float Foo::kValue = 1.5F;

// bar.h
struct Bar {
  static const float kValue;
};

// bar.cpp
#include "foo.h"
const float Bar::kValue = Foo::kValue;  // Constant initialization?

// main.cpp
#include "bar.h"
#include <iostream>

int main() { std::cout << Bar::kValue; }
  • Инициализирована ли нелокальная (постоянная) переменная Bar::kValue, имеющая статический срок хранения, посредством постоянной инициализации? (Что, в свою очередь, отвечает, инициализирован ли он с помощью статической или динамической инициализации)

Детали / мои собственные исследования

[basic.start.init] / 1 утверждает [курсив мой]:

Выполняется постоянная инициализация:

  • если каждое полное выражение (включая неявные преобразования), которое появляется в инициализаторе ссылки со статической продолжительностью хранения или продолжительностью хранения потока, является постоянным выражением (5.19), а ссылка привязана к lvalue, обозначающему объект со статической продолжительностью хранения или временной (см. 12.2);

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

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

В интерпретации последнего пункта, что Bar::kValue инициализируется посредством постоянной инициализации, если Foo::kValue является постоянным выражением. Я подозреваю, что могу найти ответ на вопрос, правда это или нет в [expr.const], но здесь я застрял.


person dfrib    schedule 21.05.2019    source источник
comment
Нет, Foo::kValue не является постоянным выражением, потому что компилятор не может видеть свой инициализатор в другом TU, а также потому, что он не является ни целым, ни перечисляемым, ни constexpr, ни временным: timsong-cpp.github.io/cppwp/n3337/expr.const#2.9   -  person Oktalist    schedule 21.05.2019
comment
@Oktalist: Foo::kValue объявлен как const, тогда это постоянное выражение. Где взять стоимость - другая проблема   -  person P. PICARD    schedule 21.05.2019
comment
@ P.PICARD Нет, константа и постоянное выражение - это разные вещи.   -  person Oktalist    schedule 21.05.2019
comment
@Oktalist спасибо. Хотели бы вы превратить это в ответ? (В качестве альтернативы попросите Сержа Баллеста добавить ссылку и обоснование к его ответу)   -  person dfrib    schedule 21.05.2019


Ответы (2)


Хм ... Я бы не стал доверять этому коду, потому что боялся бы статического фиаско с порядком инициализации. AFAIK, порядок статической инициализации между разными единицами компиляции не определен. Значит, даже тесты не дадут мне уверенности в том, что всегда все будет хорошо.

Не вдаваясь в подробности, я не смог найти в стандарте ничего, что гарантировало бы, что Foo::kValue в foo.cpp будет инициализирован до Bar::kValue в bar.cpp. И если порядок неправильный, значение в Foo::kValue будет просто неопределенным.

person Serge Ballesta    schedule 21.05.2019
comment
Я знаю, что это плохой ответ, потому что в нем нет ссылки. Я просто разместил его, чтобы предупредить OP о потенциальных проблемах. Если я ошибаюсь, просто прокомментируйте здесь, объясняя, почему, и я удалю это. Или, конечно, опубликуйте другой лучший ответ. Я обещаю проголосовать за него, если вы пингуете меня, и если я это пойму :-) - person Serge Ballesta; 21.05.2019
comment
Конечно, в стандарте C ++ 11 нет ничего, что могло бы гарантировать порядок нетривиальной статической инициализации между модулями. Вы можете положиться на тривиальную инициализацию и инициализацию constexpr во время компиляции. Вот почему синглтоны, хотя у них есть свои пожизненные проблемы. Я удивлен, что это не рассматривается в новых спецификациях, но я не думаю, что это было сделано, кроме constexpr. - person Gem Taylor; 21.05.2019
comment
Спасибо, вы правы. Oktalist предоставил ссылку, которую он может либо добавить к своему ответу, либо расширить к вашему ответу. - person dfrib; 21.05.2019
comment
Я считаю, что компилятор должен делать эту работу. Я удалил свой пост, подтверждающий эту гипотезу, потому что на самом деле в спецификации нет утверждения, подтверждающего эту идею. Но есть простой тест: Bar::kValue и Foo::kValue в зависимости друг от друга поочередно. MSVC выполняет работу правильно, исполняемый файл печатает 1.5 для обоих случаев. Но возможно, что это зависит от реализации и другие компиляторы ведут себя иначе. - person P. PICARD; 21.05.2019
comment
@ P.PICARD Меня больше всего интересует, что здесь гарантирует стандарт; Я бы не стал использовать эту конструкцию в коде продукта, так как не знаю, какие гарантии я получаю. Основываясь на комментариях Oktalist, Bar::kValue не инициализируется посредством постоянной инициализации, и мы можем (что маловероятно), с точки зрения стандарта, столкнуться с фиаско статической инициализации. - person dfrib; 21.05.2019
comment
@SergeBallesta Вы хотите обновить этот ответ, включив стандартную ссылку от Oktalist на его комментарий к вопросу? По сути, их ключевым моментом является то, что Foo::kValue не является постоянным выражением, что означает, что Bar::kValue будет инициализирован посредством динамической инициализации. В моем конкретном примере, однако, это четко определено, поскольку сам Foo::kValue инициализируется посредством постоянной инициализации, но если бы я добавил, скажем, еще одну static const float Foo::kValueTwo в Foo и инициализировал Foo::kValue значением Foo::kValueTwo, тогда оба ... - person dfrib; 22.05.2019
comment
... Foo::kValue и Bar::kValue будут инициализированы посредством динамической инициализации, что означает, что мы можем столкнуться с фиаско статической инициализации, поскольку порядок динамической инициализации между различными TU не определен. Если вы не планируете обновлять свой ответ, я обобщу эти моменты в своем собственном ответе. - person dfrib; 22.05.2019

const float не соответствует требованиям для того, чтобы быть постоянным выражением

(Этот ответ основан на Комментарий @ Oktalist, так как он воздержался от создания собственного ответа)

В следующих:

// foo.h
struct Foo {
  static const float kValue;
};

// foo.cpp
const float Foo::kValue = 1.5F;

Foo::kValue действительно инициализируется константным выражением посредством постоянной инициализации, но Foo::kValue сам по себе не является константным выражением, потому что он не является ни целым, ни перечислением, ни constexpr, ни временным. [expr.const] / 2 указывает [ акцент мой]:

< / a> является основным константным выражением, если только оно не включает одно из следующего в качестве потенциально оцениваемого подвыражения ([basic.def.odr]), но подвыражения логического И ([expr.log.and]), логическое ИЛИ ([expr.log.or]) и условным ([expr.cond]) операции, которые не оцениваются, не рассматриваются [Примечание: перегруженный оператор вызывает функцию. - конечное примечание]:

...

(2.9): преобразование lvalue-to-rvalue ([conv.lval]) если он не применяется к

  • значение glvalue целочисленного или перечисляемого типа, которое относится к энергонезависимому константному объекту с предыдущей инициализацией, инициализированному константным выражением, или
  • значение glvalue литерального типа, которое относится к энергонезависимому объекту, определенному с помощью constexpr, или которое относится к подобъекту такого объекта, или
  • значение glvalue литерального типа, которое относится к энергонезависимому временному объекту, время жизни которого еще не закончилось, инициализированному константным выражением;

Поскольку ни один из подпунктов для (2.9) здесь не применяется, Foo::kValue не является постоянным выражением. Это следует из [basic.start.init] / 2 (как указано в более ранней стандартной версии вопроса), что Bar::kValue инициализируется не с помощью постоянной инициализации, а как часть динамической инициализации.

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

Постоянная инициализация выполняется:

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

Примечание о "фиаско с порядком статической инициализации"

Обратите внимание, что этот конкретный пример не приводит к риску фиаско с порядком статической инициализации, поскольку Foo::kValue инициализируется как средство постоянной инициализации, а Bar::kValue инициализируется как часть динамической инициализации, а первый гарантированно завершится до начала динамической инициализации. .

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

Однако никогда не полагайтесь на тот факт, что этот конкретный пример имеет четко определенный порядок инициализации, поскольку небольшие изменения сделают этот факт недействительным:

// foo.h
struct Foo {
  static const float kDummyValue;
  static const float kValue;
};

// foo.cpp
const float Foo::kDummyValue = 1.5F;    // Constant initialization
const float Foo::kValue = kDummyValue;  // (!) Dynamic initialization

// bar.h
struct Bar {
  static const float kValue;
};

// bar.cpp
#include "foo.h"
const float Bar::kValue = Foo::kValue;  // (!) Dynamic initialization

// main.cpp
#include "bar.h"
#include <iostream>

int main() { std::cout << Bar::kValue; }

Как и в этом примере модификатора, инициализация Foo::kValue и Bar::kValue неопределенно упорядочена по отношению друг к другу, что означает, что Bar::kValue может быть инициализирован (со «значением» Foo::kValue) до того, как будет выполнено Foo::kValue.

person dfrib    schedule 24.05.2019