¿Por qué las plantillas de C ++ me permiten eludir tipos incompletos (declaraciones hacia adelante)?

StackOverflow https://stackoverflow.com/questions/620378

Pregunta

Probé tres iteraciones del siguiente programa simple. Este es un intento muy simplificado de escribir un par de clases de contenedor e iterador, pero tuve problemas con tipos incompletos (declaraciones de reenvío). Descubrí que, de hecho, esto era posible una vez que lo había templado todo, ¡pero solo si realmente usaba el parámetro de plantilla! (Me di cuenta de esto mirando el Código de tabla de distribución de Google .)

¿Alguna sugerencia que explique por qué la segunda funciona mientras que la tercera no? (Sé por qué la primera no funciona: el compilador debe conocer el diseño de memoria del contenedor).

Gracias de antemano.

// 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
¿Fue útil?

Solución

El primero requiere una definición de container ya que está realizando una operación de copia. Si define el constructor de iter después de la definición de container , estaría bien. Entonces:

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

El segundo ejemplo funciona porque no hay clase hasta que realmente creas una en tu función main . En ese momento todos los tipos están definidos. Intente mover cualquiera de las definiciones de plantillas de iter o container después de main y obtendrá un error.

El tercer ejemplo es una especialización para int o así aparece. Esto debería compilarse porque no se usa el parámetro de plantilla para iter . Tienes la sintaxis de especialización un poco apagada. Sin embargo, no hay un constructor adecuado, así que solo obtendrás basura para x . Por otra parte, los iteradores están bien modelados por punteros. Pasar el valor de this no será de mucha ayuda. Los iteradores suelen ser necesarios para una secuencia y no para un objeto individual. Sin embargo, no hay nada que pueda impedirle construir uno.

Y no necesita un ; después del cuerpo de una función.

Otros consejos

Puede hacer esto sin plantillas definiendo iter :: iter () después de la definición de contenedor:

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

La versión de la plantilla funciona porque al crear plantillas, ambas clases están completamente definidas.

En el primer caso, estás intentando acceder a una función miembro de la clase Contenedor antes de que la clase haya sido definida, por lo que esto no funcionará.

En el segundo caso, la plantilla se crea una instancia la primera vez que se utiliza con un tipo particular. En ese punto, la clase de Contenedor ha sido definida, en main, y así se compila.

En el tercer caso hay una referencia circular. contenedor usa iter e iter usa container, por lo que no puede funcionar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top