Подтвердить что ты не робот

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

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

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; }
};

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

4b9b3361

Ответ 1

Рассмотрим:

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;

Ответ 2

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

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

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

Также обратите внимание, что GCC 9 не учитывает это как использование odr.

Ответ 3

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

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

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

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

Ответ 4

Что-то вроде этого приходит мне на ум:

// 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);

Ответ 5

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

Не было бы этого достаточно?

std::cout << Foo<int>::dummy;