Bug 13590 - [DR39] Non-existing ambiguity when inheriting through virtuals two identical using declarations.
Summary: [DR39] Non-existing ambiguity when inheriting through virtuals two identical ...
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 3.3.3
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
Keywords: rejects-valid
Depends on:
Blocks: c++-core-issues
  Show dependency treegraph
Reported: 2004-01-06 20:54 UTC by Boris Kolpackov
Modified: 2021-12-04 10:49 UTC (History)
6 users (show)

See Also:
Host: i386-gnu-lnux
Target: i386-gnu-lnux
Build: i386-gnu-lnux
Known to work: 2.95.3
Known to fail: 3.4.1, 4.0.0
Last reconfirmed: 2020-08-26 00:00:00


Note You need to log in before you can comment on or make changes to this bug.
Description Boris Kolpackov 2004-01-06 20:54:26 UTC
$ cat >test.cpp
struct Edge

struct Node
  void f (Edge&);

struct AE : virtual Edge

struct AN : virtual Node
  void f (AE&);
  using Node::f;

struct BN : virtual Node
  using Node::f;

struct CN : virtual AN, virtual BN

void f ()
  AE e;
  CN n;
  n.f (e);

$ g++ --version
g++ (GCC) 3.3.3 20031229 (prerelease) (Debian)
$ g++ -c ./test.cpp
test.cpp: In function `void f()':
test.cpp:34: error: request for member `f' is ambiguous
test.cpp:7: error: candidates are: void Node::f(Edge&)
test.cpp:7: error:                 void Node::f(Edge&)
test.cpp:17: error:                 void AN::f(AE&)
Comment 1 Wolfgang Bangerth 2004-01-06 21:10:57 UTC
I'm no expert in "using" things, but this really seems odd: 
struct Node { 
  void f (int); 
struct AN : virtual Node { 
  using Node::f; 
struct BN : virtual Node { 
  using Node::f; 
struct CN : virtual AN, virtual BN {}; 
void f () { 
  CN n; 
  n.f (1); 
With all gcc versions since 3.2 I get this: 
g/x> /home/bangerth/bin/gcc-3.4-pre/bin/c++ -c x.cc 
x.cc: In function `void f()': 
x.cc:17: error: request for member `f' is ambiguous 
x.cc:2: error: candidates are: void Node::f(int) 
x.cc:2: error:                 void Node::f(int) 
On the other hand, gcc2.95 and icc accept the code. I have no idea 
who's right, though. 
Comment 2 Boris Kolpackov 2004-01-06 21:55:53 UTC
Subject: Re:  unexpected overload resolution

After some more thinking I tend to believe that the two examples 
above and the code below should at least behave in a consistent
manner (i.e. all fail or all compile):

namespace A
  void f (char);

namespace B
  void f (char);

namespace C
  void f (int);

void g ()
  using namespace A;
  using namespace B;
  using namespace C;

  int i;
  f (i);

GCC 3.3.3 compiles this example fine.
Comment 3 Wolfgang Bangerth 2004-01-06 23:00:19 UTC
That's not a good example. The using directive for A::f and B::f create 
an ambiguity, but this only needs to be detected when you try to call 
one of the functions, i.e. during overload resolution, not when the 
using directive is parsed. However, you call f(int), and there is no 
ambiguity in this case. 
Comment 4 Boris Kolpackov 2004-01-06 23:15:29 UTC
Subject: Re:  unexpected overload resolution

> That's not a good example. The using directive for A::f and B::f create 
> an ambiguity, but this only needs to be detected when you try to call 
> one of the functions, i.e. during overload resolution, not when the 
> using directive is parsed. 


> However, you call f(int), and there is no ambiguity in this case.

Doesn't the overload resolution happens when I call f(int) ? And
according to what you just said it should be flagged as an error.
And thus, I think, it's a very good example ;-)

What I was trying to say is that when the same processes (overload 
resolution) happens in similar situations (ambiguilty in class 
inheritance vs ambiguilty in using directive) it would be nice if 
it had the same result (consistency).
Comment 5 Wolfgang Bangerth 2004-01-07 14:00:57 UTC
There is no ambiguity in the call to f(int), because there is only 
one such function. That there might be an ambiguity for other argument 
types is irrelevant. 
There is also no ambiguity in my shortened example (and your original 
code): the base class is virtual, so there is exactly one copy and 
no ambiguity with what "this" pointer it has to be called. That doesn't 
mean, however, that a compiler is supposed to detect this special case 
of virtual base classes, though.  
Comment 6 Giovanni Bajo 2004-01-11 01:05:04 UTC
Confirmed with Wolfgang testcase:

There is only one Node base class because of the virtual inheritance, so there 
is only one Node::f function. There is no ambiguity. When the lookup is 
performed, it should be noted that the two using declarations *do* refer to the 
same declaration on the same base object.

Boris, as Wolfgang explained, your other testcase is indeed non ambigous and 
correctly accepted by GCC since, at the point of call, there is only one and 
only one "best" overload.
Comment 7 Andrew Pinski 2004-01-11 01:13:19 UTC
This has been failing since at least 2000-12-31, this is a regression from 2.95.3.
Comment 8 Nathan Sidwell 2004-03-05 14:44:43 UTC
I am not conviced this code is valid. [10.2] describes member name lookup
in an MI lattice, and says that 'each declaration that was introduced by a
using-declaration is considered to be from each subobject of C that is of the
type containg the declaration descignated by the using declaration'. ... 'if the
resulting set of declarations are not all from sbobjects of the same type ...
there is an ambiguity'.  Whilst those rules make sense for data members, they do
not for function members.  The set of overloaded functions can include some from
a base, brought in by a using declaration, and some declared in the class
containing the using declaration [7.3.3]/12   If those from the base were
considered to actually be from the base, then we'd have a set from differnt
types, so be ambiguous.

Thus, I think we have to consider overloaded functions to be from the class
containing the using declaration (and not the class being used).  If that is the
case, then the code is ill-formed, even though both using declarations refer to
the same unambiguous base object.

The original testcase had added an overload on one path through the graph, if we
augment the case with an additional overload on the other path, it quite clearly
must be ambiguous (because it would be without the using declarations).

Wolfgang's reduced test case, with just using declarations it the other
interesting example.  If we treat this as ambiguous, then it appears that (in
other useages), we could use using declarations to resolve inherited member
ambiguities -- something expressly forbidden by [10.2] (footnote 96).  So this
argues that the reduced case should be well formed.

But, now we have some strangeness.  Whether a function using declaration's
naming class is the class of the using declaration of the class of the used
declaration depends on whether there are any additional overloads.
Comment 9 Giovanni Bajo 2004-03-05 14:56:06 UTC
Nathan, can you raise this issue with the CWG? Maybe we can have some lights 
shed on this eventually. If you do that, we can suspend this bug until there is 
a clear position on the matter.
Comment 10 Mark Mitchell 2004-03-09 07:40:57 UTC
We're not even convinced yet that this is a bug, so I'm postponing this until 3.4.1.
Comment 11 Mark Mitchell 2004-05-31 21:31:36 UTC
Nathan -- is there any word from the committee on this issue?
Comment 12 Mark Mitchell 2004-05-31 21:32:04 UTC
Waiting for information from Nathan.
Comment 13 Jason Merrill 2004-06-10 20:10:52 UTC
This is a bug in the standard, which the CWG has been working on fixing for
quite a while:

Comment 14 Andrew Pinski 2004-06-10 22:19:52 UTC
Suspending as the DR report is in review.
Comment 15 Mark Mitchell 2004-06-18 23:46:34 UTC
Postponed until GCC 3.4.2.
Comment 16 Boris Kolpackov 2004-08-03 14:31:38 UTC
Just found a non-academic example when this really hurts:

template<typename x>
struct base
  void f ();

template <typename x>
struct a : virtual base<x>
  using base<x>::f;  // which f

  void g ()
    f ();

template <typename x>
struct b : virtual base<x>
  using base<x>::f; // which f

  void g ()
    f ();

struct c : a<int>, b<int> {};

f ()
  c c_;
  c_.f ();

Here I use using-declaration to hind the compiler which f() I am using since it
does not depend on template parameter in any way. The result - call to c_.f ()
is ambiguous - IMO, is ridiculous.
Comment 17 Andrew Pinski 2004-10-19 11:37:36 UTC
Unsetting the target milestone as there is a stil opened DR about this.
Comment 18 Jonathan Wakely 2005-08-31 19:27:56 UTC
As DR39 has been ruled a defect, this should be re-opened.
Comment 19 Jonathan Wakely 2005-12-10 13:17:51 UTC
would the summary be clarified by changing "Non-existing ambiguity when inhering through virtuals two identical using declarations" to "Ambiguity due to two using declarations for same member of virtual base" ?
Comment 20 gpdr-takedown-2020-09-17 2014-05-27 16:27:03 UTC
I think that non abiguous cases should be allowed. My basic inheritance structure is as follows:

01 class Base {
02 	public:
03		virtual void foo() { cout << "Base" << endl; }
04 };
05 class A : public Base {
06	public:
07		//using Base::foo;
08		//virtual void foo() { cout << "A" << endl; }
09		//virtual void foo() { Base::foo(); }
10 };
11 class B : public Base {
12	public:
13		//virtual void foo() { cout << "B" << endl; }
14 };
15 class AB : public A,B {
16	public:
17 		//using Base::foo;
18		//using A::foo;
19		//using A::Base::foo;
20		//virtual void foo() { cout << "AB" << endl; }
21 };
23 int main() {
24 	//Base *p = new AB;
25 	AB *p = new AB;
26 	//p->foo();
28 	return 0;
29 }

Currently only the following case is possible:
0.) uncomment line 09, 18, 26 which is the worst option I think

The following cases should be allowed:
1.) uncomment line 24 // virtual and non virtual functions are unambiguous
2.) uncomment line 26 // it is clear to use HW::foo() -> both paths same properties
3.) uncomment line 17 // clear to use HW::foo()
4.) uncomment line 18 (perhaps together with line 07) // clear to use the inherited A::foo() which inherits from the HW::foo()
5.) change line 11 to "class B : private Base {" // only A::foo is accessible, B::foo not
6.) doing the the cases above without virtual
Comment 21 Jonathan Wakely 2014-05-27 19:15:36 UTC
Since I'm not sure if any of the examples here are meant to be accepted (some definitely aren't) here's the example from the standard which should compile:

struct A { int x; };                    // S(x,A) = { { A::x }, { A }}
struct B { float x; };                  // S(x,B) = { { B::x }, { B }}
struct C: public A, public B { }; // S(x,C) = { invalid, { A in C, B in C } }
struct D: public virtual C { };         // S(x,D) = S(x,C)
struct E: public virtual C { char x; }; // S(x,E) = { { E::x }, { E }}
struct F: public D, public E { };       // S(x,F) = S(x,E)
int main() {
  F f;
  f.x = 0; // OK, lookup finds E::x
Comment 22 Paolo Carlini 2015-07-10 14:54:23 UTC
Not sure if Fabien is actively working on this..