9

The following program is rejected by gcc as ambiguous:

struct Aint 
{
    virtual void foo(int);
};

struct Astring 
{
    virtual void foo(std::string);
};

struct A: public Aint, public Astring {};

int main()
{
  std::string s;

  A a;
  a.foo(s);

  return 0; 
}

> vt.cpp: In function ‘int main()’: vt.cpp:13:9: error: request for
> member ‘foo’ is ambiguous
>        a.foo(s);
>          ^ vt.cpp:5:34: note: candidates are: virtual void Astring::foo(std::__cxx11::string)
>      struct Astring {virtual void foo(std::string);};
>                                   ^ vt.cpp:4:31: note:                 virtual void Aint::foo(int)
>      struct Aint {virtual void foo(int);};

Clang consistently rejects the program for the same reason:

clang -std=c++1y -c vt.cpp 

vt.cpp:13:9: error: member 'foo' found in multiple base classes of different types
      a.foo(s);
        ^
vt.cpp:4:31: note: member found by ambiguous name lookup
    struct Aint {virtual void foo(int);};
                              ^
vt.cpp:5:34: note: member found by ambiguous name lookup
    struct Astring {virtual void foo(std::string);};

I am not completely sure if I understood the lookup rules in section 10.2 correctly, so I am going through the rules in the following steps to compute the lookup set S(foo, A):

1. A does not contain `foo`, so rule 5 applies and S(foo, A) is initially empty. We need to calculate the lookup sets S(foo, Aint) and S(foo, Afloat) and merge them to S(foo, A) = {}
2. S(foo, Aint) = {Aint::foo}
3. S(foo, Afloat) = {Afloat::foo}
4. Merge S(foo, Aint) = {Aint::foo} into S(foo, A) = {} to get S(foo, A) = {Aint::foo} (second case of 6.1)
5. Merge S(foo, Afloat) = {Afloat::foo} into {Aint::foo}. This create an ambiguous lookup set because of rule 6.2

The result set is an invalid set and thus the program is ill-formed.

I am wondering why the program is being rejected so early. It should be easy for the compiler to do overload resolution in this case because both functions have identical names but different signatures, so there is no real ambiguity. Is there a technical reason that this is not done, or are there other reasons which would accept incorrect programs? Does anybody know the rational behind the decision to reject these programs so early?

Jens
  • 9,058
  • 2
  • 26
  • 43
  • did you try it on clang? seems compiler is wrong – Angelus Mortis Dec 22 '15 at 14:08
  • @AngelusMortis I've added the output from clang 3.6. I think it is correct according to the lookup rules in the standard. – Jens Dec 22 '15 at 14:11
  • 1
    Just reproduced the same results, it isn't the compiler. – Ziezi Dec 22 '15 at 14:12
  • MS Visual Studio 2013 won't compile it either, although the error message is less complete: `Error 4 error C3861: 'foo': identifier not found s:\epsitec\testco\testco.cpp 21 1 testco Error 3 error C2385: ambiguous access of 'foo' s:\epsitec\testco\testco.cpp 21 1 testco ` – Jabberwocky Dec 22 '15 at 14:14
  • Could it be related that the `string` is not initialized? – Ziezi Dec 22 '15 at 14:14
  • 1
    @simplicisveritatis , no, if you replace `std_string` with e.g `char*` the result is the same. – Jabberwocky Dec 22 '15 at 14:14
  • 5
    This is correct behavior. To quote Bjarne, ["in C++, there is no overloading across scopes"](http://www.stroustrup.com/bs_faq2.html#overloadderived). – T.C. Dec 22 '15 at 14:16
  • @T.C. but in class `A` , there is no function `foo` -> then name lookup must try to find `foo` in base classes-> finds two functions named `foo`, hence there is ambiguity error coming? , which means overload resolution is in work. – Angelus Mortis Dec 22 '15 at 14:21
  • if you place `using Astring;` before, it becomes even more interesting – Ziezi Dec 22 '15 at 14:22
  • @T.C.Thanks for the reference. I have to lookup the references (D&E and TC++PL3) given there, but that should also answer my question. – Jens Dec 22 '15 at 14:27
  • my brain is hurt now :( – Angelus Mortis Dec 22 '15 at 14:30

1 Answers1

3

In C++, there is no overloading across scopes – derived class scopes are not an exception to this general rule .Please refer for more detail. Anyway your example can be improved by specifying that you want to use both version of foo by "using" keyword. See below example.

Example program:

#include <iostream>
#include <string>

struct Aint 
{
     void foo(int){std::cout<<"\n Aint";}
};

struct Astring 
{
     void foo(std::string){std::cout<<"\n Astring";}
};

struct A: public Aint, public Astring {
    using Aint::foo;
    using Astring::foo;
    };

int main()
{
  std::string s;

  A a;
 a.foo(s);

  return 0; 
}
output:Astring
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
SACHIN GOYAL
  • 955
  • 6
  • 19
  • The original example had variadic templates in it. Something like `template struct A {virtual void foo(T);}; template struct B: public A... { /* using A...; */ };`. In this example (which is not my code, I've seen it on SO) you cannot write a using declaration because it does not work with template parameter pack expansion (see https://groups.google.com/a/isocpp.org/forum/?fromgroups#!topic/std-discussion/lscp3Bn2TkA). – Jens Dec 22 '15 at 14:51
  • @Jens : I understood your point .could you please post complete original example for better understanding – SACHIN GOYAL Dec 22 '15 at 14:55
  • I've created a new question to cover this because I think it does not fit here: http://stackoverflow.com/questions/34418749/is-there-a-work-around-for-parameter-pack-expansion-in-using-declarations – Jens Dec 22 '15 at 15:05
  • Thanks Jens . I will look into it :) – SACHIN GOYAL Dec 22 '15 at 15:10