13

Consider

struct Base { int foo(int); int foo(int, int); }; struct Child : Base { using Base::foo; int foo(int, int, int); }; 

Ideally I want to bring the Base class foo that takes only one int as a parameter into the Child class, and not the one that takes 2 ints. Is there a way I can do that? My writing using Base::foo; brings in both foo methods of Base.

6
  • 7
    Quite a weird design: even if you can do it (see answers below) imagine this :Base* bPtr = someCondition? new Base(): new Child(); bPtr->foo(3); That perfecly valid, and it's going to call Child::foo(int), which you don't want. The question is: why do you want to use inheritance (is-a relation) and act as if Child is-not-a Base? Commented Oct 3, 2017 at 14:07
  • This is a substantially cut down version of the real code. Commented Oct 3, 2017 at 14:08
  • 12
    I can understand this, but the problem remains: you are using inheritance (and possibly you have some good reasons to use it), but you want your inherited class to be considered "not-a" Base": that's code smell to me. Can you exclude now any code as the one I wrote above? can you exclude it in the future? What will happen if someone call the undesired Base::foo(int, int) on a derived instance? Commented Oct 3, 2017 at 14:16
  • 5
    I think the real solution would be to use composition over inheritance. Commented Oct 3, 2017 at 16:55
  • 1
    Look up the Liskov Substitution Principle, and check if your desired code adheres to it. If not, it's better to rethink your code, to avoid later regret. (Hint: if your subclass doesn't have one of the two functions, can you substitute superclass with subclass? ) Commented Oct 4, 2017 at 3:55

3 Answers 3

27

A using declaration will make all overloads available. You can't prevent that from happening. But you can delete the overloads you don't want, after the fact:

struct Base { int foo(int); int foo(int, int); }; struct Child : Base { using Base::foo; int foo(int, int) = delete; int foo(int, int, int); }; 

Now using the two int overloads with a Child is ill-formed. However that's not a perfect solution, since a client can still call the Base version:

Child c; c.Base::foo(0, 0); 
Sign up to request clarification or add additional context in comments.

5 Comments

That's amazing! Thank you.
@SebastianJohnHoward - Don't be too happy. There's a caveat :)
note that =delete doesn't block overload resolution from finding foo(int,int), it just makes it an error if it is chosen. This is different than "just using the one argument foo".
@Yakk - True. But I gathered from the OP that the aim is to block its availability, like overloading without a using declaration does. This is a similar effect, I think.
@StoryTeller It is a similar effect, but not the same. If you didn't using or delete, test for Child c; c.foo(7,7); would fail at overload resolution time (the overload wouldn't be found) (possibly selecting a different one). With the using and =delete, it (the two int overload) can be found and chosen via overload resolution. Only after it is chosen does an error occur.
10

It's not possible to bring the specified overloaded function into the derived class. The rule of name lookup deals with names, but the overloaded functions have the same name foo.

You can write a wrapper function at the derived class, e.g.

struct Child : Base { int foo(int x) { return Base::foo(x); } int foo(int, int, int); }; 

4 Comments

@SebastianJohnHoward The other answers don't say it's possible to bring the specified overload into the derived class, they still bring all; then delete some. My approach is not to bring them, but add the necessary one. It might depend on the use scenario.
I understand. I withdraw my first comment.
@SebastianJohnHoward - As you can see it really is about managing inclusions and exclusions. If you want to forward just a few overloads, you'd best do as this answer suggests. However, if you want to omit just a few, then my answer is shorter. With your example, they are equivalent to an extent.
I like this answer. Somebody reading this in the production code doesn't go "rare syntax" and have to stop and think about what it means when the common syntax spells it out really easily.
5

I'm going to list both solutions presented in other answers, and detail how they differ.

struct Child : Base { int foo(int x) { return Base::foo(x); } int foo(int, int, int) { std::cout << 3; return 33; } }; 

this does exactly what you want, but you have to repeat the signature.

A slightly different result is:

struct Child : Base { using Base::foo; int foo(int,int)=delete; int foo(int, int, int) { std::cout << 3; return 314; } }; 

To see how this is different, imagine we did this:

struct Grandkid : Child { using Child::foo; int foo(double, double) { std::cout << 2; return 42; } }; 

and we did:

Grandkid x; x.foo(3,4); 

In the case with =delete, this would generate a compiler error.

3,4 prefers int,int over double,double. When we =delete the int,int overload, it is still considered, selected, and we pick the =deleted one. When we instead exclude it from overload resolution, 3,4 picks double,double.

Manual forwarding excludes int,int overload from being considered. =delete does not.

This is most similar to the imaginary syntax of using Base::foo(int); (ie, only bringing in one of the parent overloads of foo).

Note that Grandkid is just one simple way to detect the difference. The important thing is there is a difference between removing something from overload resolution, and =deleteing the overload.

Live example where it works, and where it doesn't.

4 Comments

Was there ever a proposal to allow selective forwarding with a using declaration? It looks somewhat useful.
@StoryTeller I am unaware of such; I think using was intended to handle the "mass case" of wanting to include the parent's overloads into overload resolution. Forwarding methods, for when you wanted to cherry pick, already existed.
I suppose it wouldn't be any less verbose than an inline definition either, really.
BTW, fantastic comparative analysis. But I think that it's worth mentioning, Grandkid can still forward the overload from Base explicitly, if it so desires. So long as stuff are accessible of course.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.