27
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)
Fabio Dalla Libera
  • 1,297
  • 1
  • 10
  • 24
  • Good question. I guess usual overload resolution rules do not apply in `C` (since normally non templates are preferred to templates if there is a match, hence the behavior with `D`). – Alexandre C. Apr 02 '12 at 11:10
  • 3
    I would add to the OP's question: "whatever C++ standard rule enforces such a behaviour: what is the rationale behind this rule?" – Vlad Apr 02 '12 at 11:12
  • 2
    @Vlad I think the behavior is quite sensible. Not causing an error here could lead the way to many nasty bugs. Good question though. – enobayram Apr 02 '12 at 11:18
  • @enobayram: then, why there's no error for `C::f`? – Vlad Apr 02 '12 at 11:19
  • 1
    are you sure the compiler is not stopping after first error? – guga Apr 02 '12 at 11:24

5 Answers5

11

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);
John Leidegren
  • 59,920
  • 20
  • 131
  • 152
  • but why does the compiler see only `A::f`, but not `B::f`? Where is the namespace difference? – Vlad Apr 02 '12 at 11:21
  • Can you back this up with a quote from the standard? – Luchian Grigore Apr 02 '12 at 11:22
  • Currently I'm working under the assumption that it ignores all other base members, as soon as it finds one. Would be intresting to know if you can control which is being invoked by changing the order of inheritance, e.g. `struct C : public B, public A` – John Leidegren Apr 02 '12 at 11:23
  • @LuchianGrigore I'm looking for it. – John Leidegren Apr 02 '12 at 11:24
  • @John: In your quote, I cannot find what makes the compiler eliminate either of the `f`'s in the case of `struct C`. – Vlad Apr 02 '12 at 11:46
  • 1
    @Vlad: The compiler does not eliminate either of them in the case of `C`; the second paragraph applies, and the program is ill-formed because they are not all from sub-objects of the same type. – Mike Seymour Apr 02 '12 at 11:49
  • My bad, I got a bit confused by the question at first, but I think I managed to put all the pices together in the end. – John Leidegren Apr 02 '12 at 12:14
  • @MikeSeymour: Thanks, I had failed to grasp the point of that paragraph. – David Rodríguez - dribeas Apr 02 '12 at 13:04
  • @John: well, then I would repeat my question from the comments to the original post: what is the rationale behind such a rule? Does it makes any sense for struct `C` behave different from `D`? – Vlad Apr 02 '12 at 14:36
  • Unfortunatly I cannot answer that, but I would think it has to due with complexity. Basically, to prevent language abuse. Think of it like this, if you introduce members in a base class that would pollute all derrived classes and possibly break code. That really should result in a compiler error becuase the rules you based that first program on, just changed. – John Leidegren Apr 03 '12 at 09:04
1

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.]

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • Iteresting still, if we comment out `using A::f;` A::f is hidden by B::f, whereas with both `using` declarations `c.f(3)` and `c.f(3)` correctly call corresponding functions. – jrok Apr 02 '12 at 11:47
  • 1
    Except the compiler doesn't prefer `A::f` - it considers both, and fails due to the ambiguity. – Mike Seymour Apr 02 '12 at 11:53
  • @MikeSeymour, then the question is why compiler doesn't have such ambiguity in the case of `D::f` ? In this case, it seems to be a case of preference to me. – iammilind Apr 02 '12 at 12:35
  • @iammilind: Indeed; in the case of `D`, both names are considered, and the template specialisation is preferred to the non-template. I was commenting on your statement that "the compiler prefers `A::f` over `B::f`", which is wrong - in that case, it does not prefer either, hence the ambiguity error. – Mike Seymour Apr 02 '12 at 12:57
0

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);
AlexTheo
  • 4,004
  • 1
  • 21
  • 35
  • 1
    The OP's question is not _how_, it's _why_? – Vlad Apr 02 '12 at 11:26
  • The answer for a why is:A compiler doesn't know which method to call from the C class – AlexTheo Apr 02 '12 at 11:26
  • 2
    Ok... why does it know for class D? – Luchian Grigore Apr 02 '12 at 11:27
  • 1
    I guess that a compiler will apply the template construction before the inheritance. B and A classes will be compiled first and after that the inheritance on C class will be applied. – AlexTheo Apr 02 '12 at 11:30
  • @Alex: No, this won't work. How would the compiler see the templated overload _before_ applying inheritance? – Vlad Apr 02 '12 at 11:33
  • 2
    Interesting, the problem goes away if I say `using A::f; using B::f;` inside `C`s declaration. – jrok Apr 02 '12 at 11:35
  • @Vlad I believe that by reading the C class first it assumes that it needs to generate a B class after that compiler apply the inheritance like class C: public A, public B but here you have a problem because of 2 same methods. – AlexTheo Apr 02 '12 at 11:58
  • @Alex: well, `B` is not templated, just `B::f` is. But anyway, after applying inheritance, what is the difference now between `C` and `D`? They now look the same for the compiler, right? – Vlad Apr 02 '12 at 12:02
  • @Vlad: Sorry for the mistake of template class you are right is just a templated method but it doesn't matter. No C and D are very different classes C class is a child of two base classes and D is just one class and methods implementation in case of C class is still in B and A classes with a difference of D class. – AlexTheo Apr 02 '12 at 12:07
0

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.

xaero99
  • 327
  • 1
  • 5
  • Thank you, this supports the thesis that when it does not find the method, it goes up to the superclasses, generate all the possibilities and complain if there're multiple choices. as @jammilind and jrok point out, with using it generates them beforehand, and checks the best matching one – Fabio Dalla Libera Apr 02 '12 at 12:02
  • This is just an observation. Without a supporting quote from the standard, it's useless. It could be a compiler bug. – Luchian Grigore Apr 02 '12 at 12:03
  • @LuchianGrigore yes,this is why I am waiting in toggling the accepted answer tick – Fabio Dalla Libera Apr 02 '12 at 12:08
  • @FabioDallaLibera as well you should. The question is a very good one and I, for one, think it deserves a matching answer. – Luchian Grigore Apr 02 '12 at 12:09
  • 1
    It can be observed at C++ standard member name lookup section (class.member.lookup item 6 ). Sorry i cannot copy here but it also contains good description better than mine. – xaero99 Apr 02 '12 at 12:17
0

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.

RedX
  • 14,749
  • 1
  • 53
  • 76