c++ templates class-hierarchy

c++ - Ambiguo cuando dos superclases tienen una función miembro con el mismo nombre, pero firmas diferentes



templates class-hierarchy (5)

struct A { void f(int x) {} }; struct B { template<typename T> void f(T x) {} }; struct C : public A, public B {}; struct D { void f(int x){} template<typename T> void f(T x) {} }; int main(int argc, char **argv) { C c; c.f<int>(3); D d; d.f<int>(3); }

¿Cuál es la razón por la cual llamar a df está bien, pero cf da

error: request for member ‘f’ is ambiguous error: candidates are: template<class T> void B::f(T) error: void A::f(int)


Considera este ejemplo más simple:

struct A{ void f(int x){} }; struct B{ void f(float t){} }; struct C:public A,public B{ }; struct D{ void f(float n){} void f(int n){} }; int main(){ C c; c.f(3); D d; d.f(3); }

En este ejemplo, igual que el suyo, D compila pero C no.
Si una clase es derivada, el mecanismo de búsqueda de miembros se comporta de manera diferente. Comprueba cada clase base y las combina: en el caso de C ; Cada clase base coincide con la búsqueda (A :: f (int) y B :: f (float)). Al fusionarlos, C decide que son ambiguos.

Para la clase de caso D : se selecciona la versión int lugar de float porque el parámetro es un entero.


Creo que el compilador prefiere A::f (función sin plantilla) sobre B::f sin ningún motivo.
Esto parece ser un error de implementación del compilador más que un detalle dependiente de la implementación.

Si agrega la siguiente línea, la compilación va bien y se selecciona la función correcta B::f<> :

struct C : public A, public B { using A::f; // optional using B::f; };

[La parte divertida es que hasta que ::f no se incluyan en el alcance de C , se tratan como funciones ajenas].


La primera parte se debe a la búsqueda de nombre de miembro, por eso falla.

Me gustaría referirlo a: 10.2/2 Member name lookup

Los siguientes pasos definen el resultado de búsqueda de nombre en un ámbito de clase, C. Primero, se considera cada declaración para el nombre en la clase y en cada uno de sus sub-objetos de clase base. Un nombre de miembro f en un subobjeto B oculta un nombre de miembro f en un subobjeto A si A es un subobjeto de clase base B. Cualquier declaración que esté tan oculta se elimina de la consideración. Cada una de estas declaraciones que fue introducida por una declaración using se considera que proviene de cada subobjeto de C que es del tipo que contiene la declaración designada por la declaración de uso.

Si el conjunto resultante de declaraciones no provienen todas de subobjetos del mismo tipo, o el conjunto tiene un miembro no estático e incluye miembros de distintos subobjetos, existe una ambigüedad y el programa está mal formado . De lo contrario, ese conjunto es el resultado de la búsqueda.

Ahora, para el asunto con funciones de plantilla.

Según 13.3.1/7 Candidate functions and argument list

En cada caso en que un candidato es una plantilla de función, las especializaciones de plantilla de función candidata se generan usando la deducción de argumento de plantilla (14.8.3, 14.8.2). Esos candidatos se manejan como funciones candidatas de la manera habitual. Un nombre de pila puede referirse a una o más plantillas de funciones y también a un conjunto de funciones no plantillas sobrecargadas. En tal caso, las funciones candidatas generadas a partir de cada plantilla de función se combinan con el conjunto de funciones candidatas no de plantilla.

Y si continúas leyendo 13.3.3/1 Best viable function

Se considera que F1 es una mejor función si:

F1 es una función sin plantilla y F2 es una especialización de plantilla de función

Es por eso que el siguiente fragmento compila y ejecuta la función sin plantilla sin error:

D c; c.f(1);


Lo que probablemente está sucediendo es que la creación de instancias de la plantilla ocurre por separado para las clases A y B , por lo que termina en dos funciones void f(int) .

Esto no ocurre en D ya que allí el compilador conoce la función void f(int) como una especialización y, por lo tanto, no especializa T para int .


Un compilador no sabe qué método llamar desde la clase C porque el método con plantilla se transará en void f (int) en el caso del tipo int, por lo que tiene dos métodos con el mismo nombre y los mismos argumentos pero miembros de diferentes clases principales.

template<typename T> void f(T x) {}

o

void f(int)

prueba esto:

c.B::f<int>(3);

o esto para la clase A:

c.A::f(3);