Вызывает ли `использование` в шаблонном классе создание экземпляра?
Этот вопрос заключается в том, как использовать инициализацию глобальной переменной для достижения некоторого побочного эффекта перед main()
выполняется (например, регистрация фабричного класса перед main()
). Это может быть сложно, когда задействован шаблон. Рассмотрим следующий фрагмент кода:
template <class T>
class A {
public:
static bool flag;
static bool do_something() {
// Side effect: do something here.
}
template <bool&>
struct Dummy {};
// Important: Do not delete.
// This line forces the instantiation and initialization of `flag`.
using DummyType = Dummy<flag>;
};
template <class T>
bool A<T>::flag = A<T>::do_something();
// A<int>::flag is instantiated and.
// A<int>::do_something will be called before main to initialize A<int>::flag.
class B : A<int> {};
Если мы удалим using
, поведение будет другим:
template <class T>
class A {
public:
static bool flag;
static bool do_something() {
// Side effect: do something here.
}
// The `using` dummy part is missing.
};
template <class T>
bool A<T>::flag = A<T>::do_something();
// A<int>::flag is not instantiated.
// A<int>::do_something will not be called.
class B : A<int> {};
Мой вопрос: это using
трюк здесь специфическая вещь компилятора, или это поддержано стандартом C++? Я немного поискал и нашел соответствующую информацию о стандарте 14.7.1, но все еще не уверен в правилахusing
поскольку это не упоминается в 14.7.1 (мой источник - это, не уверен, следует ли его рассматривать как основную истину). Так жеusing Dummy
мне кажется несколько хакерским. Есть ли другие обходные пути, чтобы сделать его более элегантным? Цель состоит в том, чтобы разрешить создание экземпляра вclass A
так что сегменты кода, связанные с побочными эффектами, остаются закрытыми.
1 ответ
Благодаря новой формулировке в C++20 можно дать окончательный ответ на ваш вопрос. В комментариях Игорь Тандетник упомянул соответствующий абзац в C++17, а именно [temp.inst]/3. В C++20 это [temp.inst]/4, в котором есть новая релевантная формулировка, выделенная жирным шрифтом:
Если член шаблона класса или шаблона члена не является объявленной специализацией, специализация члена создается неявно, когда на специализацию ссылаются в контексте, который требует существования определения члена, или если существование определения члена влияет на семантика программы; в частности, инициализация (и любые связанные с ней побочные эффекты) статического члена данных не происходит, если сам статический член данных не используется таким образом, который требует существования определения статического члена данных.
Далее в том же разделе, пункт 8:
Считается, что существование определения переменной или функции влияет на семантику программы, если переменная или функция необходимы для постоянной оценки выражением (7.7), даже если постоянная оценка выражения не требуется или если постоянное выражение оценка не использует определение.
Короче говоря, если создается объявление-псевдоним, то статический член данных необходим для постоянной оценки, поэтому он создается.
Неформально «необходимо для постоянной оценки» относится к определенным ситуациям, когда переменная или функция не используется odr, но то, как она используется в постоянной оценке, по-прежнему требует ее определения.
Формально определение «необходимо для постоянной оценки» дано в [expr.const]/15:
Выражение или преобразование потенциально оценивается как константа, если оно:
- выражение с явно вычисляемой константой,
- потенциально оцениваемое выражение (6.3),
- непосредственное подвыражение braced-init-list,
- выражение формы
&
выражение cast , которое происходит внутри шаблонного объекта, или- подвыражение одного из вышеперечисленных, которое не является подвыражением вложенного невычисленного операнда.
Функция или переменная необходимы для постоянной оценки, если они:
- функция constexpr, названная выражением (6.3), которое потенциально оценивается как константа, или
- переменная, имя которой появляется как потенциально постоянное оцениваемое выражение, которое является либо переменной constexpr, либо имеет неизменяемый целочисленный тип с уточнением const или ссылочный тип.
«Явно оцениваемая константа» определена в стр. 14 этого раздела:
Выражение или преобразование явно вычисляются как константы, если они являются: [...] выражением-константой или [...] [Примечание 7: Выражение с явно вычисляемыми константами оценивается даже в невычисленном операнде. — примечание в конце]
« Константное-выражение » — это грамматическая продукция, которая используется в различных контекстах для обозначения условного-выражения , которое должно быть константным выражением. Мы можем проверить грамматику для простого-шаблона-идентификатора (это то, чтоDummy<flag>
является):
простой-идентификатор-шаблона :
имя-шаблона<
список-шаблонов-аргументов (необязательно)>
[...]
список-шаблонов-аргументов :
шаблон-аргументов (опция)
список-шаблонов-аргументов,
шаблон-аргумент...
(опционально)аргумент-шаблона :
постоянное-выражение идентификатор
типа
идентификатор-выражение
Здесь id-выражение относится к параметру шаблона шаблона. Вusing DummyType = Dummy<flag>;
, является аргументом для параметра шаблона, не являющегося типом, поэтому это выражение-константа .
Поскольку выражение явно является выражением, вычисляемым константами, оно потенциально является выражением, вычисляемым константами. Переменнаяflag
имя "появляется как потенциально постоянное оцениваемое выражение", и это переменная ссылочного типа, поэтому она необходима для константной оценки.
Обратите внимание, что понятие «необходимо для постоянной оценки» было введено P0589 для разрешения CWG 1581. За него проголосовали как за DR, поэтому его решение имеет обратную силу. Понятия «необходимо для постоянной оценки» и «явно оцениваемой константы» всегда должны были быть в C++; их отсутствие привело к таким проблемам, как в этом вопросе.