El problema es que el compilador no está intentando utilizar la plantilla operator<<
que proporcionó, sino una versión sin plantilla.
Cuando declara a un amigo dentro de una clase, está inyectando la declaración de esa función en el ámbito adjunto. El siguiente código tiene el efecto de declarar (y no definir) una función gratuita que toma un argumento non_template_test
por la constante referencia:
class non_template_test
{
friend void f(non_template_test const &);
};
// declares here:
// void f(non_template_test const &);
Lo mismo sucede con clases de plantilla, aunque en este caso es un poco menos intuitiva . Cuando declaras (y no defines) una función amiga dentro del cuerpo de la clase de la plantilla, estás declarando una función libre con esos argumentos exactos.Tenga en cuenta que se está declarando una función y no una función de plantilla:
template<typename T>
class template_test
{
friend void f(template_test<T> const & t);
};
// for each instantiating type T (int, double...) declares:
// void f(template_test<int> const &);
// void f(template_test<double> const &);
int main() {
template_test<int> t1;
template_test<double> t2;
}
Esas funciones se declaran libres pero no definidos. La parte difícil aquí es que esas funciones gratuitas no son una plantilla, sino que se declaran funciones gratuitas regulares. Cuando se agrega la función de plantilla en la mezcla que se obtiene:
template<typename T> class template_test {
friend void f(template_test<T> const &);
};
// when instantiated with int, implicitly declares:
// void f(template_test<int> const &);
template <typename T>
void f(template_test<T> const & x) {} // 1
int main() {
template_test<int> t1;
f(t1);
}
Cuando el compilador realiza la función principal que crea la instancia de la plantilla template_test
con el tipo int
y que declara la función libre void f(template_test<int> const &)
que no se templated. Cuando encuentra la llamada f(t1)
, hay dos símbolos f
que coinciden: la no plantilla f(template_test<int> const &)
declarada (y no definida) cuando se instanciaron template_test
y la versión de plantilla que se declara y define en 1
. La versión no templada tiene prioridad y el compilador la compara.
Cuando el vinculador intenta resolver la versión sin plantilla de f
, no puede encontrar el símbolo y, por lo tanto, falla.
¿Qué podemos hacer? Hay dos soluciones diferentes. En el primer caso, hacemos que el compilador proporcione funciones sin plantilla para cada tipo de creación de instancias. En el segundo caso, declaramos la versión con plantilla como un amigo. Son sutilmente diferentes, pero en la mayoría de los casos son equivalentes.
Tener el compilador generar las funciones no moldeados por nosotros:
template <typename T>
class test
{
friend void f(test<T> const &) {}
};
// implicitly
Esto tiene el efecto de crear tantas funciones gratuitas no moldeados según sea necesario. Cuando el compilador encuentra la declaración de amigo dentro de la plantilla test
, no solo encuentra la declaración sino también la implementación y agrega ambos al alcance adjunto.
crear una versión en plantilla un amigo
Para hacer la plantilla de un amigo hay que tenerlo ya declarada y decirle al compilador que el amigo que queremos es en realidad una plantilla y no una función gratuita no moldeado:
template <typename T> class test; // forward declare the template class
template <typename T> void f(test<T> const&); // forward declare the template
template <typename T>
class test {
friend void f<>(test<T> const&); // declare f<T>(test<T> const &) a friend
};
template <typename T>
void f(test<T> const &) {}
En este caso, antes de declarar f
como plantilla hay que remitir declarar la plantilla. Para declarar la plantilla f
primero debemos reenviar declarar la plantilla test
. La declaración de amigo se modifica para incluir los corchetes angulares que identifican que el elemento que estamos haciendo un amigo es en realidad una plantilla y no una función gratuita.
Volver al problema
Volviendo a su ejemplo particular, la solución más sencilla es tener el compilador genera las funciones para usted por inlining la declaración de la función amigo:
template <typename T>
class BinaryTree {
friend std::ostream& operator<<(std::ostream& o, BinaryTree const & t) {
t.dump(o);
return o;
}
void dump(std::ostream& o) const;
};
Con ese código está forzando al compilador a generar un operator<<
sin plantilla para cada tipo de instancia, y esa función generada delega en el método dump
de la plantilla.
¿A qué tipo de árbol se hace referencia en saveToFile? –
¿Usó espacios de nombres en su código (particularmente al declarar 'BinaryTree' y su sobrecarga de 'operator <<')? Si es así, muéstrelos en contexto. –
Por favor, publique el texto exacto del mensaje de error dado por el compilador, en su totalidad. –