Ambiguous when two superclasses have a member function with the same name, but different signatures

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

  •  28-05-2021
  •  | 
  •  

Domanda

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

What is the reason for which calling d.f is fine, but c.f gives

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

Soluzione

The first part is due to member name lookup, that's why it fails.

I would refer you to: 10.2/2 Member name lookup

The following steps define the result of name lookup in a class scope, C. First, every declaration for the name in the class and in each of its base class sub-objects is considered. A member name f in one sub-object B hides a member name f in a sub-object A if A is a base class sub-object of B. Any declarations that are so hidden are eliminated from consideration. Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration.

If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed. Otherwise that set is the result of the lookup.

Now, for the matter with template functions.

As per 13.3.1/7 Candidate functions and argument list

In each case where a candidate is a function template, candidate function template specializations are generated using template argument deduction (14.8.3, 14.8.2). Those candidates are then handled as candidate functions in the usual way. A given name can refer to one or more function templates and also to a set of overloaded non-template functions. In such a case, the candidate functions generated from each function template are combined with the set of non-template candidate functions.

And if you continue reading 13.3.3/1 Best viable function

F1 is considered to be a better function, if:

F1 is a non-template function and F2 is a function template specialization

That's why the following snippet compiles and runs the non-template function without error:

D c;
c.f(1);

Altri suggerimenti

I believe the compiler prefers A::f (non-template function) over B::f for no reason.
This seems to be a compiler implementation bug more than a implementation dependent detail.

If you add following line, then compilation goes fine and the correct function B::f<> is selected:

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

[Funny part is that until the ::f are not brought into the scope of C, they are treated as alien functions.]

A compiler doesn't know which method to call from the C class because templated method will be transormed in void f(int) in case of int type so you have two methods with the same name and same arguments but members of different parent classes.

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

or

void f(int)

try this:

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

or this for the A class:

c.A::f(3);

Consider this simpler example:

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

In this example, same as yours, D compiles but C does not.
If a class is a derived one, member lookup mechanism behaves different. It checks each base class and merges them: In the case of C; Each base class matches the lookup ( A::f(int) and B::f(float) ). Upon merging them C decides they are ambiguous.

For the case class D: int version is selected instead of float because parameter is an integer.

What is probably happening is that the template instantiation is happening separately for class A and B, thus ending in two void f(int) functions.

This does not happen in D since there the compiler knows about the void f(int) function as a specialization and therefore does not specialize T for int.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top