2009-11-24 28 views
9

Estaba luchando con el problema descrito en this question (declarando una función de plantilla como amigo de una clase de plantilla), y creo que la segunda respuesta es lo que quiero hacer (reenviar declarar la plantilla función, luego nombre una especialización como amigo). Tengo una pregunta acerca de si una solución ligeramente diferente es realmente correcto o simplemente pasa a trabajar en Visual C++ 2008.Función de amigo de plantilla de una clase de plantilla

Código de ensayo es:

#include <iostream> 

// forward declarations 
template <typename T> 
class test; 

template <typename T> 
std::ostream& operator<<(std::ostream &out, const test<T> &t); 

template <typename T> 
class test { 
    friend std::ostream& operator<< <T>(std::ostream &out, const test<T> &t); 
    // alternative friend declaration 
    // template <typename U> 
    // friend std::ostream& operator<<(std::ostream &out, const test<T> &t); 

    // rest of class 
    }; 

template <typename T> 
std::ostream& operator<<(std::ostream &out, const test<T> &t) { 
    // output function defined here 
    } 

En primer lugar, una cosa extraña que encontré fue que si cambio la declaración directa de operator<< para que no coincida (por ejemplo, std::ostream& operator<<(std::ostream &out, int fake);, todo todavía se compila y funciona correctamente (para ser claros, no es necesario definir dicha función, solo declararla). Sin embargo, como en el pregunta vinculada, la eliminación de la declaración directa causa un problema ya que el compilador parece pensar que estoy declarando un miembro de datos en lugar de una función de amigo. Estoy bastante seguro de que este comportamiento es un error de Visual C++ 2008.

Lo interesante es cuando elimino las declaraciones de reenvío y uso la declaración de amigo alternativa en el código anterior. Tenga en cuenta que el parámetro de plantilla U no aparece en la siguiente firma. Este método también se compila y funciona correctamente (sin cambiar nada más). Mi pregunta es si esto se ajusta al estándar o una idiosincrasia de Visual C++ 2008 (no pude encontrar una buena respuesta en mis libros de referencia).

Tenga en cuenta que mientras que una declaración amigo template <typename U> friend ... const test<U> &t); también funciona, en realidad esto le da a cada instancia del acceso del operador friend a cualquier instancia de test, mientras que lo que quiero es que los miembros privados de test<T> sólo deben ser accesibles desde operator<< <T>. Probé esto instanciando un test<int> dentro del operator<< y accediendo a un miembro privado; esto debería causar un error de compilación cuando intento generar un test<double>.

Sinopsis: Eliminar las declaraciones directas y cambiar a la declaración de amigo alternativa en el código anterior parece producir el mismo resultado (en Visual C++ 2008): ¿es este código realmente correcto?

ACTUALIZACIÓN: cualquiera de las modificaciones anteriores al código no funciona en gcc, así que supongo que estos son errores o "características" en el compilador de Visual C++. De todos modos, apreciaría los comentarios de personas familiarizadas con el estándar.

+1

Incidentalmente, descubrí que agregar 'using namespace std;' antes de la declaración de clase eliminará la necesidad de las declaraciones forward, que supongo que es un efecto secundario del error (posible) del compilador. –

+0

Según mi comentario, ¿puede agregar explícitamente los ejemplos (incluida la creación de instancias) que está diciendo que funcionan? Verbalmente es bastante difícil entender lo que quieres decir con cada ejemplo. –

Respuesta

7

... si cambio de la declaración delante del operador < < de modo que no se corresponde con

Una función amigo debe ser visto como un tipo muy especial de la declaración. En esencia, el compilador hace lo suficiente para analizar la declaración; sin embargo, no se realizará ninguna verificación semántica a menos que realmente se especialice en la clase.

Después de hacer su modificación sugerida, si a continuación, crea una instancia test obtendrá un error acerca de las declaraciones que no coincidan:

template class test<int>; 

... Sin embargo ... la eliminación de la declaración adelantada provoca un problema

El compilador intenta analizar la declaración para almacenarla hasta que la plantilla de clase esté especializada.Durante el análisis sintáctico, el compilador alcanza el < en la declaración:

friend std::ostream& operator<< < 

La única manera de que operator<< podría ser seguido por < es decir, si se trata de una plantilla, así que una búsqueda se lleva a cabo para comprobar que se trata de una plantilla. Si se encuentra una plantilla de función, entonces el < se considera el inicio de los argumentos de la plantilla.

Cuando elimina la declaración directa, no se encuentra ninguna plantilla y se considera que operator<< es un objeto. (Esta también es la razón por la que cuando agrega using namespace std el código continúa compilando, ya que debe haber declaraciones de plantillas para operator<<).

... cuando elimino las declaraciones de reenvío y uso la declaración de amigo alternativa en el código anterior. Tenga en cuenta que el parámetro de la plantilla U no aparece en la siguiente firma ...

No es necesario que todos los parámetros de la plantilla se utilicen en los argumentos de una plantilla de función. La declaración alternativa es para una nueva plantilla de función que solo será invocable si se declara en el espacio de nombres y especifica argumentos de plantilla explícitos.

Un simple ejemplo de esto sería:?

class A {}; 
template <typename T> A & operator<<(A &, int); 

void foo() { 
    A a; 
    operator<< <int> (a, 10); 
} 

... es el código realmente correcto ..

Bueno, hay dos partes en este. La primera es que la función friend alternativa no se refiere a la declaración más adelante en el ámbito de aplicación:

template <typename T> 
class test { 
    template <typename U> 
    friend std::ostream& operator<<(std::ostream &out, const test<T> &t); 
    }; 

template <typename T> 
std::ostream& operator<<(std::ostream &out, const test<T> &t); // NOT FRIEND! 

La función amigo en realidad sería declarado en el espacio de nombres para cada especialidad:

template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<int> &t); 
template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<char> &t); 
template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<float> &t); 

Cada especialización de operator<< <U> tendrá acceso a la especialización específica según el tipo de su parámetro test<T>. Entonces, en esencia, el acceso está restringido según lo requiera. Sin embargo, como he mencionado antes de que estas funciones son básicamente inservible como operadores, ya que se debe utilizar la sintaxis llamada de función:

int main() 
{ 
    test<int> t; 
    operator<< <int> (std << cout, t); 
    operator<< <float> (std << cout, t); 
    operator<< <char> (std << cout, t); 
} 

De acuerdo con las respuestas a la pregunta anterior, use o bien la declaración hacia adelante como lo sugiere litb, o ir con la definición de la función amigo en línea según la respuesta Dr_Asik's (que probablemente sería lo que haría).

ACTUALIZACIÓN: segundo comentario

... cambiando la declaración hacia adelante antes de la clase; el de la clase sigue coincidiendo con la función que implemento después ...

Como he señalado anteriormente, el compilador comprueba si operator<< es una plantilla cuando se ve la < en la declaración:

friend std::ostream& operator<< < 

Lo hace buscando el nombre y comprobando si se trata de una plantilla.Siempre que tenga una declaración ficticia de reenvío, esto "engaña" al compilador para que trate a su amigo como un nombre de plantilla, por lo que se considera que el < es el comienzo de una lista de argumentos de plantilla.

Más adelante, cuando crea una instancia de la clase, tiene una plantilla válida para que coincida. Básicamente, estás engañando al compilador para que trate al amigo como una especialización de plantilla.

Puede hacer esto aquí porque (como dije antes), no se realiza ninguna comprobación semántica en este momento.

+0

Originalmente pensé que funcionó de manera parecida a la que describiste, pero no es así (por eso hice esta pregunta). Cuando dije "todo compila y funciona correctamente", quise decir que pude crear una instancia de la clase y usar el operador '<<' de la forma habitual. También verifiqué que la función del operador tenía el acceso correcto (por ejemplo, no podía acceder a los miembros privados de una 'prueba ' si se llamaba para imprimir una 'prueba '. Este es el caso con el código tal como está escrito, con un declaración directa "incorrecta", y con la declaración alternativa de amigo. –

+0

También me di cuenta de que probablemente entendió mal lo que dije acerca de la declaración "falsa" - estoy hablando solo de cambiar la declaración adelante antes de la clase; la del class aún coincide con la función que implemento más tarde (por lo que en instanciación, la plantilla de función necesaria ya se ha analizado). –

+0

@Sumudu: He agregado una actualización para abordar su segundo comentario. En cuanto a la primera, ¿puede modificar su pregunta para incluir el ejemplo que dice "compila y funciona correctamente" pero utiliza la plantilla de función de amigo alternativa? Cuando hago eso aquí, obtengo errores para el acceso a los miembros en la plantilla de espacio de nombres. –

Cuestiones relacionadas