Почему шаблоны С++ позволяют мне обходить неполные типы (форвардные объявления)?

Я пробовал три итерации следующей простой программы. Это очень упрощенная попытка написать пару классов контейнера и итератора, но я столкнулся с проблемами с неполными типами (форвардные объявления). Я обнаружил, что это было действительно возможно, как только я все заплатил за шаблон, но только если я действительно использовал параметр шаблона! (Я понял это, посмотрев Google, который может быть изменен.

Любые подсказки, объясняющие, почему вторая работает, а третья - нет? (Я знаю, почему первый не работает - компилятор должен знать макет памяти контейнера.)

Спасибо заранее.

// This doesn't work: invalid use of incomplete type.
#if 0
struct container;
struct iter {
  container &c;
  int *p;
  iter(container &c) : c(c), p(&c.value()) {}
};
struct container {
  int x;
  int &value() { return x; }
  iter begin() { return iter(*this); }
};
int main() {
  container c;
  c.begin();
  return 0;
}
#endif

// This *does* work.
template<typename T> struct container;
template<typename T> struct iter {
  container<T> &c;
  T *p;
  iter(container<T> &c) : c(c), p(&c.value()) {}
};
template<typename T> struct container {
  T x;
  T &value() { return x; }
  iter<T> begin() { return iter<T>(*this); }
};
int main() {
  container<int> c;
  c.begin();
  return 0;
};

// This doesn't work either.
#if 0
template<typename T> struct container;
template<typename T> struct iter {
  container<int> &c;
  int *p;
  iter(container<int> &c) : c(c), p(&c.value()) {}
};
template<typename T> struct container {
  int x;
  int &value() { return x; }
  iter<int> begin() { return iter<int>(*this); }
};
int main() {
  container<int> c;
  c.begin();
  return 0;
}
#endif

Ответ 1

Первое требует определения container, так как вы выполняете операцию копирования. Если вы определяете конструктор iter после определения container, все будет в порядке. Итак:

struct container;
struct iter {
  container &c;
  int *p;
  iter(container &c);
};

struct container {
  int x;
  int &value() { return x; }
  iter begin() { return iter(*this); }
};

iter::iter(container &c) : c(c), p(&c.value()) {}

int main() {
  container c;
  c.begin();
  return 0;
}

Второй пример работает, потому что нет класса до тех пор, пока вы его не создадите в своей функции main. К этому времени все типы определены. Попытайтесь перенести любое из шаблонов iter или container после main, и вы получите ошибку.

Третий пример - это специализация для int или, таким образом, он появляется. Это должно компилироваться, потому что параметр шаблона для iter не используется. У вас есть синтаксис специализации. Тем не менее, нет соответствующего конструктора, поэтому вы можете получить мусор для x. Более того, итераторы хорошо моделируются указателями. Передача значения this не поможет. Итераторы обычно требуются для последовательности, а не для отдельного объекта. Хотя, нет ничего, что может помешать вам построить его.

И вам не нужно ; после тела функции.

Ответ 2

Вы можете сделать это без шаблонов, определив iter:: iter() после определения контейнера:

struct container;

struct iter {
  container &c;
  int *p;
  iter(container &c);
};

struct container {
  int x;
  int &value() { return x; }
  iter begin() { return iter(*this); }
};

iter::iter(container &c)
    : c(c), p(&c.value()) {}

int main() {
  container c;
  c.begin();
  return 0;
}

Версия шаблона работает, потому что, когда вы создаете шаблоны, оба класса полностью определены.

Ответ 3

В первом случае вы пытаетесь получить доступ к функции-члену класса Container до того, как класс был определен, поэтому это не сработает.

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

В третьем случае имеется круговая ссылка. контейнер использует iter и iter использует контейнер, поэтому он не может работать.