Принудительное создание шаблона с помощью typedef: успех в g++, сбой в Visual С++
Я хочу форсировать создание шаблона.
Приведенный ниже код работает (выведите 1
) на g++ (http://coliru.stacked-crooked.com/a/33986d0e0d320ad4).
Однако в Visual C++ (https://rextester.com/WGQG68063) он печатает неверный результат (0
).
#include <iostream>
#include <string>
template <int& T>struct NonTypeParameter { };
//internal implementation
int lala=0;
template <typename T> struct Holder{
static int init;
};
template <typename T> int Holder<T>::init = lala++;
//tool for user
template <typename T> struct InitCRTP{
using dummy=NonTypeParameter<Holder<T>::init>;
};
class WantInit : public InitCRTP<WantInit>{};//user register easily
int main(){
std::cout << lala << std::endl;
}
Это ошибка компилятора Visual C++ или какое-то неопределенное поведение?
Если это ошибка Visual C++, как ее обойти (при этом оставаясь красивой)?
Изменить: Изменить класс → структура, как рекомендовал Макс Лангоф (и многие люди). Поблагодарить.
Щедрость Причина
С противоположными решениями от StoryTeller и Максима Егорушкина и их всесторонним обсуждением (спасибо!) Это звучит как нечеткая область правила C++.
Если это ошибка Visual C++, я хочу, чтобы проблема была достаточно достоверной, чтобы сообщить о ней.
Более того, я все еще желаю хорошего обходного пути, потому что этот метод очень полезен для создания пользовательских идентификаторов типов. Явная реализация не так удобна.
Примечание. Я вручил награду Каенбю Рин, потому что для меня это легко понять.
Это не означает, что остальные ответы являются менее правильными или менее полезными.
Я все еще не уверен, что является правильным. Читатели должны действовать с осторожностью.
В целях безопасности я предполагаю, что я просто не могу использовать эту функцию (пока). Всем спасибо.
Ответы
Ответ 1
Конечно, есть ошибка компилятора. Мы можем проверить это, немного изменив InitCRTP
:
template <typename T, typename = NonTypeParameter<Holder<T>::init>>
struct InitCRTP {
};
Теперь, ссылаясь на любую специализацию InitCRTP<T>
, необходимо использовать Holder<T>::init
для определения второго аргумента шаблона. Это, в свою очередь, должно вызвать создание экземпляра Holder<T>::init
, и все же VS не создает этого.
В общем, использование класса CRTP в качестве основы должно было бы создать все объявления внутри класса, включая объявление dummy
. Так что это тоже должно было сработать.
Мы можем проверить это дальше. Объявления функций-членов создаются вместе с классом, когда используются в качестве базы:
template <typename T> struct InitCRTP{
using dummy=NonTypeParameter<Holder<T>::init>;
void dummy2(dummy);
};
Тем не менее, VC++ упрямый. Учитывая все это, и поведение, демонстрируемое как Clang, так и GCC, это ошибка VC++.
Ответ 2
class WantInit : public InitCRTP<WantInit>
не создает ни InitCRTP<WantInit>::dummy
, ни Holder<WantInit>::init
, потому что они не упоминаются чем-то, фактически используемым в программе. Неявная цепочка создания экземпляров в вашем коде не требует создания экземпляров Holder<T>::init
, см. неявное создание экземпляров:
Это относится к членам шаблона класса: , если член не используется в программе, он не создан и не требует определения.
Исправление заключается в использовании явной реализации шаблона:
template struct Holder<void>;
Это вызывает создание экземпляра TG45 вместе со всеми его элементами, не являющимися шаблонами.
Кроме того, вы можете создать экземпляр только Holder<T>::init
члена, например:
static_cast<void>(Holder<void>::init);
IMO, gcc и clang слишком стремятся создавать экземпляры вещей, на которые нет ссылок. Такое поведение не нарушает и не отклоняет допустимый код, так что это вряд ли ошибка, но в зависимости от такого специфического поведения побочные эффекты являются хрупкими и непереносимыми.
Ответ 3
Давайте попробуем определенно использовать ODR для члена init
.
#include <iostream>
#include <string>
int lala=0;
template <typename T> struct Holder{
static int init;
};
template <typename T> int Holder<T>::init = lala++;
template <typename T> struct InitCRTP{
InitCRTP() { (void)Holder<T>::init; }
};
class WantInit : public InitCRTP<WantInit>{};
int main(){
std::cout << lala << std::endl;
// WantInit w; <---------------------------- look here
}
Теперь результат программы меняется, если закомментированная строка не закомментирована. Состояние создания шаблона IMHO или состояние использования ODR чего-либо не может зависеть от того, была ли вызвана какая-либо не шаблонная функция (в данном случае конструктор WantInit). Я бы сказал, что есть довольно сильный запах жука.
Ответ 4
Я считаю, что @MaximEgorushkin прав насчет того факта, что dummy
на самом деле не создан.
dummy
объявлен (потому что это псевдоним типа объявление), и для того, чтобы объявить этот псевдоним, NonTypeParameter<Holder<T>::init>
объявлен. Чтобы объявить NonTypeParameter<Holder<T>::init>
, должен быть объявлен его шаблонный параметр Holder<T>::init
, поэтому также объявлен Holder<T>
.
Стандарт требует, чтобы при создании экземпляра класса шаблона были определены его удаленные функции-члены. [temp.spec]
Неявная реализация специализации шаблона класса вызывает: [...]
—— Неявное создание определений удаленных функций-членов, перечислений членов с незаданной областью и анонимных объединений-членов.
А ссылка на void
должна привести к ошибке компиляции.
Мы можем использовать это, чтобы проверить, является ли определенный шаблон специализированным или нет.
#include <iostream>
#include <string>
template <int& T, typename U> struct NonTypeParameter {
U& f() = delete;
};
//internal implementation
int lala = 0;
template <typename T> struct Holder {
T& f() = delete;
static int init;
};
template <typename T> int Holder<T>::init = lala++;
//tool for user
template <typename T> struct InitCRTP {
using dummy = NonTypeParameter<Holder<T>::init, void>;
};
class WantInit : public InitCRTP<WantInit> {};//user register easily
int main() {
std::cout << lala << std::endl;
}
Этот код компилируется, потому что NonTypeParameter<Holder<T>::init, void>
только объявлен, но не создан.
Но если мы изменим class WantInit : public InitCRTP<WantInit>
на
class WantInit : public InitCRTP<void>
Не удается скомпилировать в MSVC, g++ и clang.
Это связано с тем, что для объявления NonTypeParameter<Holder<void>::init, void>
требуется неявная реализация Holder<void>
.
Проблема, с которой столкнулся OP, связана исключительно с незнанием MSVC того, что Holder<T>::init
используется для ODR:
#include <iostream>
template <int& T> struct NonTypeParameter { };
int lala = 0;
template <typename T> struct Holder {
static int init;
};
template <typename T> int Holder<T>::init = lala++;
int main() {
NonTypeParameter<Holder<int>::init> odr;
std::cout << lala << std::endl;
}
MSVC выдаст 0
. Это означает, что он не понимает, что Holder<int>::init
использовался ODR.
Компилятор Explorer Link