2010-05-12 13 views
6

¿Por qué¿Cómo hacer amistad con el constructor de una clase con plantillas?

class A; 
template<typename T> class B 
{ 
private: 
    A* a; 

public: 
    B(); 
}; 


class A : public B<int> 
{ 
private:  
    friend B<int>::B<int>(); 
    int x; 
}; 


template<typename T> 
B<T>::B() 
{ 
    a = new A; 
    a->x = 5; 
} 

int main() { return 0; } 

resultado en

../src/main.cpp:15: error: invalid use of constructor as a template
../src/main.cpp:15: note: use ‘B::B’ instead of ‘B::class B’ to name the constructor in a qualified name

sin embargo, el cambio de friend B<int>::B<int>() a friend B<int>::B() resultados en

../src/main.cpp:15: error: no ‘void B::B()’ member function declared in class ‘B’

mientras se quita la plantilla completamente

class A; 
class B 
{ 
private: 
    A* a; 

public: 
    B(); 
}; 


class A : public B 
{ 
private: 
    friend B::B(); 
    int x; 
}; 


B::B() 
{ 
    a = new A; 
    a->x = 5; 
} 

int main() { return 0; } 

compila y ejecuta perfectamente, a pesar de que mi IDE dice que amigo B :: B() es una sintaxis no válida?

+0

Visual C++ 2008 no acepta 'amigo B :: B()' tampoco, pero Comeau sí. –

+0

Visual C++ 2008 acepta 'amigo B :: B ()' aunque, incluso con extensiones de idioma deshabilitadas. ¿Por qué GCC (4.1) no lo aceptará? – Kyle

+1

En caso de que alguien más no sepa (no lo hice), los constructores _pueden ser declarados amigos: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#263 –

Respuesta

5

Per la resolución a CWG defect 147 (la resolución fue incorporado en C++ 03), la forma correcta para nombrar un constructor nontemplate de una especialización plantilla de clase es:

B<int>::B(); 

y no

B<int>::B<int>(); 

Si se permitiera esto último, existe una ambigüedad cuando se tiene una especialización de plantilla de constructor de una especialización de plantilla de clase: ¿sería el segundo <int> para la plantilla de clase o la plantilla de constructor? (Ver el informe de defectos vinculado anteriormente para obtener más información al respecto)

Por lo tanto, la forma correcta de declarar el constructor de una especialización de plantilla de clase como un amigo es:

friend B<int>::B(); 

Comeau 4.3.10.1 e Intel C++ 11.1 ambos aceptan esa forma. Ni Visual C++ 2008 ni Visual C++ 2010 aceptan ese formulario, pero ambos aceptan el formulario (incorrecto) friend B<int>::B<int>(); (archivaré un informe de defectos en Microsoft Connect).

gcc no acepta ningún formulario anterior a la versión 4.5. Bug 5023 se informó contra gcc 3.0.2, pero la resolución solicitada en el informe de error era la forma no válida. Parece que la resolución a bug 9050 también resuelve este problema y gcc 4.5 acepta la forma correcta. Georg Fritzsche lo verificó en un comentario a la pregunta.

+1

Publiqué un informe de defectos en Microsoft Connect: https://connect.microsoft.com/VisualStudio/feedback/details/558796/. Solo pude reproducir el problema cuando la especialización de plantilla de clase es una clase base de la clase que intenta hacerse amiga del constructor. Una vez que elimina la herencia del código, Visual C++ parece manejar la declaración de amigo correctamente. Si puede encontrar un caso de prueba más simple, siéntase libre de publicarlo como un comentario en ese informe de defectos. –

+0

Curiosamente, dejaron la regla extraña en la cláusula 5.1.1/6 (FCD) que dice "Donde class-name :: class-name se usa, y los dos nombres de clase se refieren a la misma clase, esta notación nombra al constructor (12.1). ". Esta regla no solo es inútil en presencia de 3.4.3.1/2 y lo contradice ('B :: B ' nombraría cualquier constructor que no sea una plantilla). Tenía la impresión de que se suponía que el problema # 147 eliminaba esa extraña regla.Creo que la cláusula 5 (que es sobre expresiones) es el lugar equivocado para tales reglas de todos modos (y la correcta es 3.4). –

+0

@Johannes: Creo que §12.1/1 (C++ 03 y FCD) tiene el mismo problema: "... el nombre de clase del constructor seguido de una lista de parámetros se usa para declarar o definir el constructor". –

0

Supongo que estás en el territorio de las rarezas aquí con amigos constructores con plantilla. Esto se compila y funciona bien en VS2010, pero produce un desbordamiento de la pila cuando el constructor predeterminado de A llama al constructor predeterminado de B, que vuelve a crear la instancia A.

+0

Sí, definitivamente no quiero intentar crear una instancia de esas clases que escribí, jeje. Observe el explícito 'int main() {return 0; } 'Completé allí, es decir," no intente hacer nada más ":-) – Kyle

1

¿Y el motivo por el que su IDE muestra al amigo B :: B() como una sintaxis inválida en este último caso? Un error IDE.

Una solución alternativa que encontré en gcc para el caso de la plantilla si no puede actualizar es mover la implementación de B() a una función miembro, anular B :: init() y otorgarle amistad. Apuesto a que esto también interrumpe tu IDE.

Incluso si usted consigue esto para compilar, sin embargo, se encontrará con el problema de desbordamiento de pila tan pronto como se intenta crear una instancia de un B.

+0

Es cierto que no parece que 'A' deba heredar de' B' y eso resolvería el problema de recursión infinita. –

1

¿No ayuda de un typedef? p.ej.

class A : public B<int> 
{ 
    typedef B<int> Base; 
    friend Base::Base(); 
    int x; 
}; 

EDIT: El Comité de Final Draft para C++ 0x incluye el siguiente texto en el apartado 3.4.3.1 [class.qual]:

In a lookup in which the constructor is an acceptable lookup result and the nested-name-specifier nominates a class C: if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (Clause 9), or if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id’s template-name in the last component of the nested-name-specifier, the name is instead considered to name the constructor of class C.

suena como el nombre (Base) especifica después de la nombre-nested-especificador (Base::) es el mismo que el identificador en el último componente de la nombre-nested-especificador, por lo que este código hace nombrar un constructor

Pero no puedo reconciliar esto con la sección 12.1 [class.ctor]:

Because constructors do not have names, they are never found during name lookup

¿En serio? ¿Cómo funciona ese lenguaje en 3.4.3.1?

A typedef-name shall not be used as the class-name in the declarator-id for a constructor declaration.

Esto parece bastante claro, excepto que la sección 12.1 aparece solamente para discutir la declaración de la introducción de un constructor como párrafo 1 excluye el nombre de -anidado-especificador, friend y using. Si se aplica a un amigo declaraciones, también parece prohibir friend Base::B();

A special declarator syntax using an optional sequence of function-specifiers (7.1.2) followed by the constructor’s class name followed by a parameter list is used to declare or define the constructor.

Esos función especificadores son inline, virtual, explicit y constructores no pueden ser virtual de todos modos.

+0

Lamentablemente, no, ya que el constructor no se puede nombrar de esa manera. Tendría que ser 'friend Base :: B()', que es correcto pero que Visual C++ no acepta, o 'amigo Base :: B ()', que es incorrecto pero que Visual C++ acepta. –

+0

@James: En su opinión, ¿se refiere el párrafo 3 de 12.1 solo a la declaración de introducción (en clase) como el párrafo 1, o también a las declaraciones 'friend' y' using'. –

+0

No creo que la cita de §3.4.3.1/2 sea del FCD - en el FCD, la segunda parte que comienza en "o si el nombre especificado ..." tiene el prefacio de "en una declaración de uso que es una declaración de miembro, "entonces eso no aplica aquí. Mi pensamiento actual es que §12.1/3 se aplica cada vez que se declara el constructor, incluso en una declaración de amigo. Estoy de acuerdo en que la forma en que está redactado prohíbe 'friend Base :: B();'. Sin embargo, no estoy seguro de que esa fuera la intención. Estoy tratando de encontrar la propuesta o el defecto que resultó en la adición de la frase, pero no tuve suerte. –

Cuestiones relacionadas