Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguity with [] operator and multiple inheritance [duplicate]

Tags:

c++

Consider the following class:

class Foo
{
    public:

    void operator [] (const std::string& s) { }

    void operator [] (std::size_t idx) { }
};

Here, given an instance of Foo f, the expression f[0] is not ambiguous, because the compiler chooses the second overload. Likewise, the expression f["abc"] is not ambiguous, because the compiler chooses the first overload (since a const char* is convertible to an std::string).

So, why is it then, that if we have two Base classes, each with a different overload, that there is suddenly ambiguity?

Suppose we have:

class Base1
{
    public:

    void operator [] (const std::string& s) { }
};

class Base2
{
    public:

    void operator [] (std::size_t idx) { }
};

class Derived : public Base1, public Base2
{ };

Now, if we say:

Derived d;
d[0];

The compiler complains:

    error: request for member ‘operator[]’ is ambiguous
      d[0];
         ^
   note: candidates are: void Base2::operator[](std::size_t)
      void operator [] (std::size_t idx) { }
           ^
   note:                 void Base1::operator[](const string&)
      void operator [] (const std::string& s) { }

Why does the fact that both operator overloads are now in Base classes cause any ambiguity? And is there some way to resolve this?

EDIT: Could this be a compiler bug (I am using GCC 4.8.1)

like image 928
Channel72 Avatar asked Oct 09 '13 15:10

Channel72


1 Answers

This is not an issue with overload resolution, but rather with member name lookup, which is defined in 10.2. Consider (as I'd rather not write operator[] everywhere):

struct base1 { void f(int); };
struct base2 { void f(double); };
struct derived : base1, base2 {};
int main() {
   derived d; d.f(0);
}

When lookup for f starts in the postfix expression d.f(0), it will first look into derived and find that f does not resolve to anything at all. 10.2/5 then requires that lookup proceeds to all base classes in parallel, constructing separate lookup sets. In this case, S(f,base1) = { base1::f } and S(f,base2) = { base2::f }. The sets are then merged following the rules in 10.2/6. The first bullet deals with merging when one of the sets is empty or if the lookup for the different sets ended with the same member (consider that it hit a common base). The second bullet is the interesting one, as it applies here

10.2/6 bullet 2

Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous: the new S(f,C) is a lookup set with an invalid declaration set and the union of the subobject sets. In subsequent merges, an invalid declaration set is considered different from any other.

That is, S(f,base1) differs from S(f,base2), so S(f,derived) becomes an invalid declaration set. And lookup fails.

like image 176
David Rodríguez - dribeas Avatar answered Nov 15 '22 22:11

David Rodríguez - dribeas