Почему шаблоны С++ позволяют мне обходить неполные типы (форвардные объявления)?
Я пробовал три итерации следующей простой программы. Это очень упрощенная попытка написать пару классов контейнера и итератора, но я столкнулся с проблемами с неполными типами (форвардные объявления). Я обнаружил, что это было действительно возможно, как только я все заплатил за шаблон, но только если я действительно использовал параметр шаблона! (Я понял это, посмотрев 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 использует контейнер, поэтому он не может работать.