Artur Pyszczuk
Artur Pyszczuk

Reputation: 1930

Multiple inheritance from instantiations of template class and what about access to member functions

Let's take a look at the code:

template <typename C>
class S {
public:
    void add (C c) { ++cnt; }
    size_t size () const { return cnt; }

private:
    size_t cnt {}; 
};

struct Foo1 {};
struct Foo2 {};
struct Foo3 {};

class Z : public S<Foo1>, public S<Foo2>, public S<Foo3> {
public:
    using S<Foo1>::add;
    using S<Foo2>::add;
    using S<Foo3>::add;

    using S<Foo1>::size;    // (1)
    using S<Foo2>::size;    // (2)
    using S<Foo3>::size;    // (3)
};

And usage looks like this:

Z z;

z.add (Foo1 {});
z.add (Foo1 {});
z.add (Foo2 {});

cout << z.size () << endl;

This code compiles fine with gcc-5.1 (c++11), but this code does not compile under clang-3.5 (c++11 - sorry, I do not have newer version of clang).

Clang produces "error: call to member function 'size' is ambiguous" which is basically (from my point of view) correct, but gcc compiles it and returns 2.

Ok, but here is much more fun, if I switch the order of lines marked with comments (1) and (2), to get something like this:

using S<Foo2>::size;    // (2)
using S<Foo1>::size;    // (1)

The code still compiles on gcc and the result is: 1.

As you can imagine, if you write line (3) before these two, you will get 0.

So, from what I see, gcc gets first using declaration of S<C>::size, ignores rest of them and uses this one.

Could anybody tell me which compiler is doing correct work according to the C++ standard?

Reported at https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66698

Best, Artur

Upvotes: 4

Views: 117

Answers (1)

Christophe
Christophe

Reputation: 73376

It's a gcc bug. Clang and MSVC correctly flag the ambiguity.

If you remove the 3 using statements, the compilation fails (as it should) because of ambiguity:

prog.cpp:39:12: error: request for member 'size' is ambiguous
  cout << z.size () << endl; return 0;
            ^
prog.cpp:9:12: note: candidates are: size_t S<C>::size() const [with C = Foo3; size_t = unsigned int]
     size_t size () const { cout<<typeid(C).name()<<endl; return cnt; }
            ^
prog.cpp:9:12: note:                 size_t S<C>::size() const [with C = Foo2; size_t = unsigned int]
prog.cpp:9:12: note:                 size_t S<C>::size() const [with C = Foo1; size_t = unsigned int]

According to the standard member lookup algorithm, despite the using decalartions you should come to the same results:

10.2/3: (...) In the declaration set, using-declarations are replaced by the members they designate, and type declarations (including injected-class-names) are replaced by the types they designate.

I couldn't find an exact match for this bug. I'd suggest that you report it.

Upvotes: 1

Related Questions