Как принудительно инициализировать статический член?

Рассмотрим этот пример кода:

template<class D>
char register_(){
    return D::get_dummy(); // static function
}

template<class D>
struct Foo{
    static char const dummy;
};

template<class D>
char const Foo<D>::dummy = register_<D>();

struct Bar
    : Foo<Bar>
{
    static char const get_dummy() { return 42; }
};

(Также на Ideone.)

Я ожидал, что dummy будет инициализирован, как только будет конкретный экземпляр Foo, который у меня есть с Bar. Этот вопрос (и стандартная цитата в конце) объяснен довольно ясно , почему этого не происходит.

[...] в частности, инициализация (и любые связанные с ней побочные эффекты) статического члена данных не происходит, если сам статический член данных не используется таким образом, который требует существования определения статического члена данных.

Есть ли способ принудительно dummy инициализироваться (фактически вызывая register_) без любого экземпляра Bar или Foo (без экземпляров, поэтому без обмана конструктора) и без пользователя Foo нужно каким-то образом явно указать член? Дополнительные файлы cookie, чтобы производный класс ничего не делал.


Изменить: нашел способ с минимальным влиянием на производный класс:

struct Bar
    : Foo<Bar>
{   //                              vvvvvvvvvvvv
    static char const get_dummy() { (void)dummy; return 42; }
};

Хотя мне все равно хотелось бы, чтобы производный класс не делал этого. : |


person Xeo    schedule 21.06.2011    source источник
comment
Обычно мы не хотим, чтобы компилятор инициализировал все неиспользуемые переменные во всех шаблонах классов. Как компилятор должен знать, что в данном случае вы этого хотите? Фактически используя это?   -  person Bo Persson    schedule 21.06.2011
comment
@Bo: Конечно, но я бы хотел скрыть это использование от производного класса / внешнего мира и предпочел бы каким-то образом перенести это в сам Foo. : /   -  person Xeo    schedule 21.06.2011
comment
@Xeo: static char const get_dummy() { (void)dummy; return 42; } - Я сомневаюсь, что эта циклическая зависимость между Bar::get_dummy() и Foo<Bar>::dummy гарантированно работает (по стандарту). Похоже, трюк сильно зависит от реализации. Я ошибся?   -  person Serge Dundich    schedule 21.06.2011
comment
@Serge: А почему бы и нет? dummy в get_dummy - это, конечно, неинициализированный, но это не имеет значения. :) Я вообще-то им не пользуюсь.   -  person Xeo    schedule 21.06.2011
comment
@Xeo: Манекен в get_dummy, конечно, неинициализированный, но это не имеет значения. :) Я вообще-то им не пользуюсь. Да. Но почему предполагается, что Foo<Bar>::dummy инициализируется? И что должно помешать компилятору оптимизировать оператор (void)dummy; (который не имеет никакого эффекта).   -  person Serge Dundich    schedule 21.06.2011
comment
@Serge: заставляет dummy инициализироваться, потому что он используется, как указано. w.r.t. оптимизации, я должен это проверить.   -  person Xeo    schedule 21.06.2011
comment
@Xeo: заставляет манекен инициализироваться, потому что он используется Не на самом деле. Он используется только в том случае, если вы куда-нибудь звоните get_dummy(). Статические члены и глобальные переменные инициализируются либо перед первым оператором main, либо перед первым использованием любого объекта или функции, определенных в той же единице перевода, что и этот статический член или глобальная переменная. Таким образом, полностью разрешено вообще не инициализировать Foo ‹Bar› :: dummy, если вы не используете какие-либо объекты или функции из той же единицы трансляции, где создается специализация шаблона Foo.   -  person Serge Dundich    schedule 21.06.2011
comment
@Serge: Нет, он считается использованным только потому, что присутствует в качестве утверждения. Одного этого достаточно, чтобы компилятор инициализировал его, по крайней мере, на каждом компиляторе, на котором я это тестировал (MSVC и GCC .: P)   -  person Xeo    schedule 21.06.2011
comment
@Xeo: ‹< Нет, это считается использованным только потому, что это заявление. ›› Почему вы так думаете? Есть ссылки на стандарт? Прочтите главу 12.6 ISO / IEC 14882-2003. То, что показывают ваши тесты, означает именно то, что зависит от реализации. Кроме того, вы точно не знаете, каков был точный момент и причина динамической инициализации Foo<Bar>::dummy. Может быть достаточно одного struct Bar: Foo<Bar> {...} определения (это приведет к созданию экземпляра специализации Foo ‹Bar›).   -  person Serge Dundich    schedule 21.06.2011
comment
@Serge: Нет, одного определения недостаточно. Выньте (void)dummy, и звонок не будет: ideone.com/Kukds   -  person Xeo    schedule 22.06.2011
comment
@Xeo: Выньте (недействительный) манекен, и вы не увидите звонка. Я подтверждаю такое поведение. Но все же это зависит от реализации. Стандарт не требует инициализации dummy, даже если вы добавляете (void)dummy к Bar::get_dummy(). Результат для любой конкретной реализации вообще не аргумент. Стандартные операторы C ++ - это.   -  person Serge Dundich    schedule 22.06.2011
comment
Заголовок должен читаться как экземпляр, а не инициализированный ... эти термины часто неправильно меняются местами   -  person M.M    schedule 10.01.2020
comment
этот трюк требует, чтобы somwhere позвонил get_dummy хотя бы один раз?   -  person dragonxlwang    schedule 21.01.2021


Ответы (5)


Учитывать:

template<typename T, T> struct value { };

template<typename T>
struct HasStatics {
  static int a; // we force this to be initialized
  typedef value<int&, a> value_user;
};

template<typename T>
int HasStatics<T>::a = /* whatever side-effect you want */ 0;

Это также возможно без представления какого-либо участника:

template<typename T, T> struct var { enum { value }; };
typedef char user;

template<typename T>
struct HasStatics {
  static int a; // we force this to be initialized
  static int b; // and this

  // hope you like the syntax!
  user :var<int&, a>::value,
       :var<int&, b>::value;
};

template<typename T>
int HasStatics<T>::a = /* whatever side-effect you want */ 0;

template<typename T>
int HasStatics<T>::b = /* whatever side-effect you want */ 0;
person Johannes Schaub - litb    schedule 23.06.2011
comment
Первый, кажется, работает в GCC, но бесполезен в MSVC10, инициализация не происходит. : / Второй работает с ни GCC, ни MSVC10. :( Изменить: Ой, подождите, с включенным C ++ 0x он работает в GCC . По-прежнему не повезло с MSVC ... - person Xeo; 24.06.2011
comment
@Xeo у меня работает без c ++ 0x на gcc4.6. У меня нет другого компилятора, кроме clang. На clang второй тоже не работает. PR уже в пути. - person Johannes Schaub - litb; 24.06.2011
comment
О Боже! что значит user :var<int&, a>::value? - person pure cuteness; 24.06.2011
comment
@pure: это безымянное битовое поле, фактически char : 0; - person Xeo; 24.06.2011

Мы можем использовать простой трюк, основанный на объявлении, которое должно быть создано с классом:

template<…>
struct Auto {
  static Foo foo;
  static_assert(&foo);
};
template<…> Foo Auto::foo=…;

Обратите внимание, что некоторые компиляторы предупреждают о сравнении с нулевым значением; этого можно избежать с помощью &foo==&foo, (bool)&foo или ((void)&foo,true), если необходимо.

Также обратите внимание, что GCC 9.0–9.2 не считает это использование odr < / а>.

person Davis Herring    schedule 10.10.2019
comment
Самый элегантный вариант, спасибо. Я немного волнуюсь, что называю это элегантным, но, эй, это же C ++ :) - person Mikhail; 15.03.2021

Мне в голову приходит что-то вроде этого:

// in some c++ file (to make i with internal linkage)
static int i = init_dummy(Foo<int>::dummy);

где init_dummy определяется следующим образом:

int init_dummy(...)
{
  return 1;
}

Из-за переменных args вы можете поместить туда больше инициализаций, например:

static int i = init_dummy(Foo<int>::dummy, Foo<double>::dummy, Foo<whatever>::dummy);
person ovanes    schedule 23.06.2011

Как вы проверяете значение, установленное Bar. Я изменил ваш код и добавил в панель еще одну функцию:

....
static char const get_dummy(int){return Foo<Bar>::dummy;}
....

и это дает мне именно ожидаемый результат. Возможно, я неправильно понимаю, чего именно вы хотите достичь?

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

person Orochi    schedule 22.06.2011

Есть ли способ принудительно инициализировать манекен (фактически вызывая register_) без какого-либо экземпляра Bar или Foo (без экземпляров, поэтому без обмана конструктора)?

Разве этого не было бы достаточно?

std::cout << Foo<int>::dummy;
person StackedCrooked    schedule 21.06.2011
comment
Я надеюсь, что есть другие способы, кроме того, чтобы пользователь явно указывал член. : | - person Xeo; 21.06.2011