Ниже приведен недопустимый код:
struct foo {
struct bar;
bar x; // error: field x has incomplete type
struct bar{ int value{42}; };
};
int main() { return foo{}.x.value; }
Это совершенно очевидно, поскольку foo::bar считается неполным в точке, где определяется foo::x.
Однако, похоже, существует «обходной путь», который делает правильным определение того же класса:
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
struct bar{ int value{42}; };
};
using foo = foo_impl<>;
int main() { return foo{}.x.value; }
Это работает со всеми основными компиляторами. У меня есть три вопроса по этому поводу:
template) считается недействительной? Если компилятор может определить второй вариант, я не вижу причин, по которым он не сможет определить первый.Если я добавлю явную специализацию для void:
template <typename = void>
struct foo_impl {};
template<>
struct foo_impl<void> {
struct bar;
bar x; // error: field has incomplete type
struct bar{ int value{42}; };
};
using foo = foo_impl<>;
int main() { return foo{}.x.value; }
Это еще раз не может скомпилировать.
@Barry, но тогда почему он работает с «неявной» специализацией, а не с «явной» специализацией?
Что ж, с явной специализацией это «очевидно» неправильно, но разве это не «очевидно» неправильно?
@Barry, не очень "законный" аргумент :) Но я думаю, что понимаю, почему это может происходить. В вашем ответе одно из условий такого поведения: «если контекст, из которого делается ссылка на специализацию, зависит от параметра шаблона». При явной специализации контекст не зависит от параметра шаблона, поэтому правило создания экземпляра foo_impl<void>::bar непосредственно перед определением foo_impl<void> не применяется.





Я отвечу на третью часть вашего вопроса - как IANALL (не языковой юрист).
Код недействителен по той же причине, что недопустимо использовать функцию до ее объявления - даже несмотря на то, что компилятор может выяснить, какой должна быть функция, перейдя дальше в той же единице преобразования. И случаи аналогичны также в том смысле, что если у вас есть просто объявление без определения, этого достаточно для компилятора, а здесь у вас есть определение шаблона до создания экземпляра.
Итак, суть в следующем: Стандарт языка требует, чтобы компилятор не смотрел вперед для вас., когда вы хотите что-то определить (а шаблон класса не является определением класса).
Да, мне ясно, почему у компилятора будет больше проблем с первой версией кода - но это техническая деталь компилятора. Компилятор должен делать то, что предписывает стандарт языка, а не наоборот. Этот ответ выглядит как «второй пример работает, потому что компилятор реализован определенным образом», что означает, что ответ на первый вопрос - «причуда компилятора» (в этом случае вопрос 3 не имеет значения).
@GoranFlegar: Я сказал, что я не языковой юрист. Я представил обоснование, как я это вижу.
Я думаю, что этот пример явно разрешен
17.6.1.2 Member classes of class templates [temp.mem.class]
1 A member class of a class template may be defined outside the class template definition in which it is declared. [Note: The member class must be defined before its first use that requires an instantiation (17.8.1) For example,
template<class T> struct A { class B; }; A<int>::B* b1; // OK: requires A to be defined but not A::B template<class T> class A<T>::B { }; A<int>::B b2; // OK: requires A::B to be defined—end note ]
Это тоже должно быть в порядке с Работа:
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
};
template<typename T>
struct foo_impl<T>::bar{ int value{42}; };
using foo = foo_impl<>;
int main()
{
return foo{}.x.value;
}
Итак, теперь, когда комментарии были удалены, я снова подчеркну, что эта цитата просто указывает где, вы можете определить bar. Он ничего не говорит о том, можете ли вы иметь член bar до того, как он будет определен (где бы он ни был определен).
Реальный ответ может быть ¯ \ _ (ツ) _ / ¯, но, вероятно, в настоящее время это нормально, потому что шаблоны волшебны, но может быть более явно не годным до решения некоторых других основных проблем.
Во-первых, основная проблема конечно [class.mem] / 14:
Non-static data members shall not have incomplete types.
Вот почему ваш пример без шаблона плохо сформирован. Однако, согласно [temp.point] / 4:
For a class template specialization, a class member template specialization, or a specialization for a class member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization, if the context from which the specialization is referenced depends on a template parameter, and if the specialization is not instantiated previous to the instantiation of the enclosing template, the point of instantiation is immediately before the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.
Это предполагает, что для foo_impl<void>::bar создается экземпляр передfoo_impl<void>, и, следовательно, он завершается в точке, где создается экземпляр нестатических данных типа bar. Так что, может, все в порядке.
тем не мение, проблемы с основным языком 1626 и 2335 имеют дело с не совсем такими же, но все еще довольно похожими проблемами, касающимися полноты и шаблонов, и оба указывают на желание сделать случай шаблона более согласованным с случаем без шаблона.
Что все это означает в целом? Я не уверен.
В нем говорится: «Если специализация создается неявно, потому что на нее имеется ссылка из другого шаблона из другой специализации шаблона». Действительно ли это у нас есть? «если контекст, из которого делается ссылка на специализацию, зависит от параметра шаблона» У нас этого нет. Верный?
@Barry, добро пожаловать обратно! :) Единственная проблема, которую я здесь вижу, заключается в том, что нигде в моем примере нет специализации шаблона (только аргументы по умолчанию! = Специализация). Мне пришлось бы где-нибудь написать template<> foo_impl<void> { /* specialization */ };, чтобы иметь такой сценарий.
Забавно то, что когда я делать специализирую его для void, он снова не компилируется: / Я обновлю вопрос, включив это.
@Goran К сожалению, стандарт использует специализацию в нескольких контекстах. Это может сбивать с толку. vector<int> v; приводит к созданию экземпляра специализации шаблона класса vector
@Barry да, не знал этого, в стандарте это четко сказано в [temp.spec] / 4. Может быть, добавьте это к своему ответу, чтобы другим людям было понятнее.
@ Rakete1111 Я начинаю думать, что у нас здесь такая ситуация: «если специализация создается неявно, потому что на нее ссылаются из другой специализации шаблона» -> на специализацию foo_impl<void>::bar ссылаются изнутри специализации foo_impl<void> (что происходит в foo{}.x.value), когда определяя переменную foo_impl<void>::x. «если контекст, из которого делается ссылка на специализацию, зависит от параметра шаблона» -> контекст (определение шаблона класса foo_impl) зависит от параметра шаблона, здесь он заменен на void.
Я (условно) приму это сейчас, так как это наиболее правдоподобное объяснение, которое у нас есть - это сделает его первым ответом, который появится и, надеюсь, подтолкнет других людей к его подтверждению / опровержению.
@Barry bar не зависит, поэтому [temp.point] не может применяться. Вы можете специализировать бар, но это не значит, что он зависимый.
@ Rakete1111 bar зависит от eel.is/c++draft/temp.dep#type-9.3
Я не уверен, что принятый ответ является правильным объяснением, но на данный момент он наиболее правдоподобен. Экстраполируя этот ответ, вот ответы на мои первоначальные вопросы:
template) считается недействительной? Если компилятор может определить второй вариант, я не вижу причин, по которым он не сможет определить первый. [Потому что C++ странный - он обрабатывает шаблоны классов иначе, чем классы (вы, наверное, уже догадались об этом). ]При создании экземпляра foo{} в main компилятор создает экземпляр (неявной) специализации для foo_impl<void>. Эта специализация ссылается на foo_impl<void>::bar в строке 4 (bar x;). Контекст находится в определении шаблона, поэтому он зависит от параметра шаблона, а специализация foo_impl<void>::bar, очевидно, не была ранее создана, поэтому все предварительные условия для [temp.point] / 4 выполняются, и компилятор генерирует следующий промежуточный (псевдо) код:
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
struct bar{ int value{42}; };
};
using foo = foo_impl<>;
// implicit specialization of foo_impl<void>::bar, [temp.point]/4
$ struct foo_impl<void>::bar {
$ int value{42};
$ };
// implicit specialization of foo_impl<void>
$ struct foo_impl<void> {
$ struct bar;
$ bar x; // bar is not incomplete here
$ };
int main() { return foo{}.x.value; }
Согласно [temp.spec] / 4:
A specialization is a class, function, or class member that is either instantiated or explicitly specialized.
поэтому вызов foo{}.x.value в исходной реализации с шаблонами квалифицируется как специализация (для меня это было чем-то новым).
Версия с явной специализацией не компилируется, поскольку кажется, что:
if the context from which the specialization is referenced depends on a template parameter
больше не выполняется, поэтому правило из [temp.point] / 4 не применяется.
Извините, что снова беспокою вас по этому поводу, но [temp.point] относится к зависимым именам. Ни bar, ни x не зависят. Да, вы можете их специализировать, но это не значит, что это зависит.
@ Rakete1111 а это не bar, а foo_impl<>::bar?
@Swift 3 человека здесь (включая меня) уже пытались объяснить это, см. Его ответ на его интерпретацию стандарта (в основном, «bar всегда имеет семантику пустого класса, поэтому он не зависит»).
@Swift Это не имеет значения. Я не говорю, что это всегда один и тот же bar, но просто для разных экземпляров одного и того же шаблона bar имеет одно и то же определение (но они не одинаковы). Но я перестану спорить, это непродуктивно.
по 3-му пункту я могу добавить, что на самом деле это не странно. При условии соблюдения всех требований к шаблонам, того факта, что порядок объявления не влияет на результат подстановки, парадигмы SFINAE и того факта, что шаблоны могут быть рекурсивными, а их реализация создается из контекста существующего кода, это единственный способ, которым шаблоны могут быть реализованы без серьезных причуды. По сути, компилятор должен выполнять отдельный проход после перевода всего модуля. Чтобы первая версия была действительной, компилятор должен выполнить два прохода, и возникает проблема с одним определением.
Да, здесь есть специализации по шаблонам. Специализации не обязательно должны быть явными.
foo- это специализацияfoo_impl