Неполный тип не разрешен в классе, но разрешен в шаблоне класса - PullRequest
0 голосов
/ 06 мая 2018

Следующий код неверен:

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

работает со всеми основными компиляторами. У меня есть три вопроса по этому поводу:

  1. Это действительно правильный код C ++ или просто причуды компиляторов?
  2. Если это допустимый код, есть ли в стандарте C ++ параграф, касающийся этого исключения?
  3. Если это действительный код, почему первая версия (без 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; } 

Еще раз не может скомпилировать .

Ответы [ 4 ]

0 голосов
/ 07 мая 2018

Подробнее о принятом ответе

Я не уверен, что принятый ответ является правильным объяснением, но на данный момент он наиболее правдоподобен. Экстраполируя этот ответ, я отвечаю на мои первоначальные вопросы:

  1. Это действительно допустимый код C ++ или просто причуды компиляторов? [ Это действительный код. ]
  2. Если это допустимый код, есть ли в стандарте C ++ параграф, который касается этого исключения? [ [точка темпа] / 4 ]
  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 :

Специализация - это класс, функция или член класса, который либо создан, либо явным образом специализирован.

, поэтому вызов foo{}.x.value в исходной реализации с шаблонами квалифицируется как специализация (для меня это было чем-то новым).

О версии с явной специализацией

Версия с явной специализацией не компилируется, поскольку кажется, что:

если контекст, на который ссылается специализация, зависит от параметра шаблона

больше не выполняется, поэтому правило из [temp.point] / 4 не применяется.

0 голосов
/ 06 мая 2018

Я думаю, что этот пример явно разрешен

17.6.1.2 Классы-члены шаблонов классов [temp.mem.class]

1 Класс члена шаблона класса может быть определен вне определения шаблона класса, в котором он объявлен. [Примечание: класс-член должен быть определен до его первого использования, которое требует создания экземпляра (17.8.1), например,

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

- Конечная заметка]

Это должно работать тоже хорошо:

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;
}
0 голосов
/ 06 мая 2018

Реальный ответ может быть ¯ \ _ (ツ) _ / ¯, но, вероятно, в настоящее время все в порядке, потому что шаблоны магические, но это может быть более явно не в ожидании решения некоторых других основных проблем.

Во-первых, главная проблема, конечно, это [class.mem] / 14 :

Нестатические члены данных не должны иметь неполные типы.

Вот почему ваш пример не по шаблону неверен. Однако согласно [temp.point] / 4 :

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

Что предполагает, что foo_impl<void>::bar создается до foo_impl<void>, и, следовательно, завершается в момент, когда создается экземпляр нестатического элемента данных типа bar. Так что, может быть, все в порядке.

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

Что все это значит, если рассматривать в целом? Я не уверен.

0 голосов
/ 06 мая 2018

Я отвечу на третью часть вашего вопроса - как IANALL (не адвокат по языкам).

Код недействителен по той же причине, по которой недопустимо использовать функцию до того, как она была объявлена ​​- даже если компилятор может выяснить, какой должна быть функция, перейдя далее в том же модуле перевода. И случаи аналогичны также в том смысле, что если у вас просто есть объявление без определения, этого достаточно для компилятора, а здесь у вас есть определение шаблона до создания экземпляра.

Итак, дело в том: Стандарт языка требует, чтобы компилятор не смотрел в будущее , когда вы хотите что-то определить (а шаблон класса не является определением класса).

...