15

What should happen for this case:

struct A {
  void f();
};

struct B : virtual A {
  using A::f;
};

struct C : virtual A {
  using A::f;
};

struct D : B, C { 
  void g() {
    f();
  }
};

The line of interest is f(). Clearly the lookup of f according to 10.2 of the FDIS succeeds and finds A::f. However, what candidates will overload resolution consider? The spec says at 13.3.1p4:

For non-conversion functions introduced by a using-declaration into a derived class, the function is considered to be a member of the derived class for the purpose of defining the type of the implicit object parameter.

The intent of this is that for a single class, if such a class contains both own member functions and a using declaration bringing names of base class functions into scope, that during overload resolution all the function candidates have the same class type in their implicit object parameter. But what does this mean for the above example? Will the candidates be the following?

void F1(B&)
void F2(C&)
// call arguments: (lvalue D)

This appears to be wrong, because we only have one declaration in the lookup result set according to 10.2p7. How shall we interpret this??

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212

4 Answers4

1

I think that since the lookup set resulting from 10.2/7 results in only one declaration, there's no function overloading present at all. 13.3.1/4 would only apply when/if the lookup set resulting from 10.2/7 contained two or more declarations.

Edit: Perhaps I wasn't as clear as I'd hoped. Even if f is overloaded in A, I think most of the same reasoning applies. Perhaps it's best to take things step by step. (Note, that in this case, I'm using the same S(f, X) notation as the standard, but since your most derived class is D, your S(f, D) corresponds to their S(f, C), and your S(f, B) ans S(f, C) correspond to its S(f, B1) and S(f, B2).

First s(f, D) is empty, because we have no declaration of f directly contained in D. Based on that, we get to 10.2/5.

In 10.2/6, we start by merging s(f, B) into S(f, D). Since s(f, D) is currently empty, we follow the second condition under the first bullet point, and S(f, D) becomes a copy of S(f, B).

Then we have to merge S(f, C) into S(f, D). In this case, each of the subobject members of S(f, C) is a subobject member of S(f, D). This satisfies the first condition of the first bullet point, so we leave S(f, D) unchanged, and the merge is complete.

At that point, there are no more base classes Bi to consider, so our S(f, D) = S(f, B). None of the declarations from S(f, C) is present in the final overload set at all.

Then, if S(f, B) contained two or more functions, we proceed to 13.3.1, and resolve the overload set -- but since the entire set came via B, the situation posited in the question simply doesn't exist.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • @Jerry thanks for the answer. That's a point where the spec isn't clear about. 13.3.1.1.1 says for instance "Of interest in 13.3.1.1.1 are only those function calls in which the postfix-expression ultimately contains a name that denotes one or more functions that might be called." (see also http://www.eggheadcafe.com/software/aspnet/36285261/cases-when-overload-resolution-applies.aspx). Anyway, we can declare a second function inside `A`, like `void f(int);`, and then for sure have overload resolution apply. To the issue asked about in the question, I don't think that makes a difference. – Johannes Schaub - litb Apr 16 '11 at 01:36
  • "each of the subobject members of S(f, C) is a subobject member of S(f, D)" -> The state of `S(f, D)` at that point is `{ { A::f }, { B in D } }`, and `S(f, C)` is `{ { A::f }, { C in D } }` so I cannot see why that is (C is not a subobject of B, nor vice versa). Can you please explain that part? – Johannes Schaub - litb Apr 16 '11 at 02:16
  • @Johannes: Just to be sure I understand you, you're figuring that at point that the last bullet point should apply, and we should form a union of the subject sets/shared set of declarations, correct? – Jerry Coffin Apr 16 '11 at 02:24
  • @Jerry yes, that's what I think should be done. I think the first bullet is for carrying out the dominance rule (described by the note in paragraph 10). In our case there is no dominance. – Johannes Schaub - litb Apr 16 '11 at 02:25
  • @Johannes: My thinking (perhaps mistaken) was that it talks about the *subobject members* of Bi, not about Bi itself, so I considered that set to include `A`, which *is* a subobject of C. Rereading it, however, it's not clear (at least to me) whether they mean members of the set, or members of the class. I was reading it as referring to class members, but I think you're probably right that it's intended to refer to set members. – Jerry Coffin Apr 16 '11 at 02:28
  • @Johannes: By your reading, can "the subobject members of S(f,Bi)", ever be anything except Bi? As an aside, if they're going to describe the operations in terms of sets, wouldn't it be easier to just use unambiguous set notation and be done with it? – Jerry Coffin Apr 16 '11 at 02:59
  • @Jerry yes. For example the subobject members of `S(f, D)` are `C, B`. If D were to be a base of another class E, then for `S(f, E)`, when we consider `S(f, Bi)` with `Bi` being `D`, then the subobject members would be `C, B`. – Johannes Schaub - litb Apr 16 '11 at 03:02
  • @Johannes: Okay, by that logic, when Bi is `C`, shouldn't `A` be a subobject member? (and I'm honestly not trying to catch you out here or anything, just trying to understand things...) – Jerry Coffin Apr 16 '11 at 03:05
  • @Jerry `S(f, C)` will find the using declaration. So that declaration is added to `S(f, C)` (and replaced by the target of the using declaration). The subobject added is `C`. Then calculation of `S(f, C)` is complete. Paragraphs 2 and 3 define this. I've read that paragraph counless of times, and I too had trouble following things first. So you really don't need to worry about it :) – Johannes Schaub - litb Apr 16 '11 at 03:14
  • @Johannes: Yes -- I've reread it a few times myself, but I'm pretty sure I need to reread it and probably sleep on it before I'll get it straight. The slow response of Usenet wasn't always bad -- for questions like this that take serious study and thought, it was actually kind of a good thing... – Jerry Coffin Apr 16 '11 at 03:31
  • @Jerry, @Johannes: It's not *subobject members*, but *direct base class subobjects*. `A` is in that set, because it is inherited virtually, and then the dominance rule applies, leaving only `A`. – Ben Voigt Apr 16 '11 at 17:41
0

Only speculation, totally not sure. :)

[ Example:
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
}
S(x, F) is unambiguous because the A and B base subobjects of D are also base subobjects of E, so S(x,D)
is discarded in the first merge step. —end example ]

Is the example from 10.2p7, where S(f,C) denotes the lookup set. The sentence provided at the end is essential: Since both D and E have the same C base class, and E::x hides the x from that C, making the final use of F::x unambigiuous.
Now, in your example, nothing hides the f of the base classes of D, so the use of D::f is still ambigiuous and I can't see how 10.2p7 applies to your case. Like said at the top, totally not sure. ;)

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • p3 says "In the declaration set, using-declarations are replaced by the members they designate", so we will always only find `A::f`, so that in the end the result is `{ { A::f }, { B in D, C in D } }`. – Johannes Schaub - litb Apr 16 '11 at 01:15
  • From your question: "For non-conversion functions introduced by a using-declaration into a derived class, the function is considered to be a member of the derived class". So wouldn't that apply so there would be `{ { B::f, C::f }, { B in D, C in D } }` and thus it would be ambiguous ? – Xeo Apr 16 '11 at 01:21
0

Instead of addressing the question directly, I'm going to try to argue that pretending there exist f functions in each of the derived classes doesn't work:

There's only one candidate function, and it has type

void A::(void)

Although you can form a pointer-to-member to that function with

void (A::*F0)(void) = &A::f;
void (B::*F1)(void) = F0;
void (C::*F2)(void) = F0;

This is because the pointer-to-member contains additional information necessary to calculate the parameter of the function. The pointer-to-member call site finds an A subobject of the actual target instance, to provide the this pointer of f. There's no logic inside the function to find A's members from a this pointer of derived type. So one can't talk about void F1(B* this) and void F2(C* this) as your question suggests.

If the functions are considered members of the derived class, it is as

void B::A::f(void);
void C::A::f(void);

But as B::A and C::A are the same base class, ultimately there is only one function in the candidate list, although it is in the list twice. Then virtual inheritance provides that both candidates are calling the same function on the same object, there is no ambiguity.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 1
    However, the problem I see is that that *one* candidate function was "introduced by a using-declaration into a derived class" *twice*. Consider `struct A { void f(short); }; struct B : A { void f(long); using A::f; };`. Now, `B b; b.f(0);` will be ambiguous, because both candidates will have `B&` as implicit object parameter: `A::f` is considered to be a member of `B`. But what member do we consider `A::f` to be in my example in the question? – Johannes Schaub - litb Apr 16 '11 at 04:08
  • Also, I don't know what this has to do with pointer to members. – Johannes Schaub - litb Apr 16 '11 at 13:22
  • @johannes: pointer-to-member allows you to treat `A::f` as if it takes a hidden `this` parameter of type `B*`. But that relies on extra machinery in the pointer-to-member, the actual function does not take `B*` as its `this` pointer and cannot be treated as if it does. – Ben Voigt Apr 16 '11 at 17:16
  • see 13.3.1 and the description of implicit object parameters. Also, I don't know what you mean by "If the functions are considered members of the derived class, it is as ...". – Johannes Schaub - litb Apr 16 '11 at 17:21
0

I think the key is in 10.2p5, where the standard refers to checking each "direct base class subobject".

Because A is inherited virtually, it is a "direct base class subobject" of D (10.1p4).

Then three subobjects of D are considered: A, B, and C. By 10.2p6 B and C are eliminated (A is a base of these), and only A::f is a candidate.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720