10

I was surprised by the fact that GCC does not consider the call to foo() in the following program ambiguous:

#include <iostream>

struct B1 { bool foo(bool) { return true; } };
struct B2 { bool foo(bool) { return false; } };

struct C : public B1, public B2
{
    using B1::foo;
    using B2::foo;
};

int main()
{
    C c;

    // Compiles and prints `true` on GCC 4.7.2 and GCC 4.8.0 (beta);
    // does not compile on Clang 3.2 and ICC 13.0.1;
    std::cout << std::boolalpha << c.foo(true);
}

The above function call compiles and returns true on GCC 4.7.2 and GCC 4.8.0 (beta), while it won't compile (as I would expect) on Clang 3.2 and ICC 13.0.1.

Is this a case of "no diagnostic required", or is it a bug in GCC? References to the C++11 Standard are encouraged.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Build failed on VC11. Ambiguous call error. – Mark Garcia Feb 26 '13 at 01:51
  • @MarkGarcia: Yes, it seems only GCC accepts it. The question is whether this is due to a bug or no diagnostic is required for this kind of error. – Andy Prowl Feb 26 '13 at 01:51
  • FWIW, g++ 4.4.3 gives an error, but not when using `foo` in `main`, but on the `using`s in `C` already: `ambig.cc:9: error: using declaration 'using B2::foo' conflicts with a previous using declaration` – us2012 Feb 26 '13 at 02:14
  • @us2012: That is indeed what I would expect from any compiler, but it's interesting to see that this behavior changes between versions of the same compiler. – Andy Prowl Feb 26 '13 at 02:17
  • @Andy Yeah, I thought it was strange that all of g++4.6, g++4.8, clang3.2, icc13 locate the error in line 18 when trying to call `foo` rather than in line 9 when the ambiguous `using` declaration is made. – us2012 Feb 26 '13 at 02:19
  • @AndyProwl: It's only an error if you use the function. Anyway, `using` is about a name, not a specific override. So B1::foo and B2::foo might be different, possibly intersecting, sets of overrides, and the non-shared overrides are fine. Furthermore, despite the `using`, you could override the intersecting overrides with an explicit declaration in C, so there might not be any ambiguity possible. (10.2(1) "Name lookup can result in an ambiguity, in which case the program is ill-formed.") – rici Feb 26 '13 at 02:24
  • @rici: That's also true. I've searched the word "diagnostic" in the Standard and I found a few occurrences that *might* be relevant, but at the moment I am not able to figure out if they really are. – Andy Prowl Feb 26 '13 at 02:27
  • @AndyProwl, I'm pretty sure that it's an ambiguity, and ambiguities must be diagnosed. But, as I said, according to 10.2(1), ambiguities are only present when a lookup is attempted; it's fine to drag two competing declarations into a scope if you never try to use it. – rici Feb 26 '13 at 02:35
  • @rici: Yes, makes sense that the using declaration should not cause an error. However, I am actually calling `foo()`, so at that point a diagnostic should be emitted, unless this falls into some "no diagnostic required" case of course. – Andy Prowl Feb 26 '13 at 02:39

2 Answers2

4

§7.3.3/3:

In a using-declaration used as a member-declaration, the nested-name-specifier shall name a base class of the class being defined. If such a using-declaration names a constructor, the nested-name-specifier shall name a direct base class of the class being defined; otherwise it introduces the set of declarations found by member name lookup (10.2, 3.4.3.1).

¶14:

… [ Note: Two using-declarations may introduce functions with the same name and the same parameter types. If, for a call to an unqualified function name, function overload resolution selects the functions introduced by such using-declarations, the function call is ill-formed.

¶16:

For the purpose of overload resolution, the functions which are introduced by a using-declaration into a derived class will be treated as though they were members of the derived class.

So, the using declarations are legal, but the functions are peers in the same overload set, as you said, and the program is ill-formed.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
1

The call to foo(true) in your program is, as you say, clearly ambiguous; furthermore, it is ambiguous according to the algorithm presented in §10.2 and consequently, it should be flagged on use. (Flagging the using declaration would be incorrect; 10.2(1) clearly states that ambiguous uses of names are flagged on lookup, not on declaration.)

It's interesting to contrast this program with a similar one, which is the subject of a a recognized gcc bug (slightly modified from that bug report to make the parallel clearer):

#include <iostream>

struct A {
   static int foo() {return 1;}
   static int foo(char) { return 2;}
};

struct B1 : A {
//   using A::foo;
};
struct B2 : A {
//   using A::foo;
};

struct C : B1, B2 {
//   using B1::foo;
//   using B2::foo;
};

int main()
{
    std::cout << C::foo();
}

The above program is correct; despite the diamond inheritance, foo is a static member of A, so it is not ambiguous. In fact, gcc compiles it without trouble. However, uncommenting the two instances of using A::foo, which does not change anything about either foo, causes gcc to produce the oddly reduplicated error noted in the bug report. Uncommenting the two using declarations inside C, which presumably triggers the other bug which is the subject of this question, then masks the static function bug and causes the program to compile again.

clang seems to handle all possible variants of this program, for what it's worth.

Finally, note that an explicitly declared foo(bool) within C (in the original program) will win out over any foo(bool) brought into C's scope by using declarations. I suspect that both of these bugs are the result of bad bookkeeping while trying to keep track of the various function declarations in each class's scope and their individual provenance (as a sequence of using declarations and function declarations).

rici
  • 234,347
  • 28
  • 237
  • 341