1

I have a situation where I am using a public API and need to "override" a function in a parent class, however this function has not been declared as virtual. Although it is hacky, I have decided that I am going to change the visibility of the parent class function using the code mentioned here: a way in c++ to hide a specific function.

I am, however, facing an issue, in that the parent class has an overload of this function with very similar parameters, and I am therefore getting the error "ambiguous call to overloaded function", even though I have made the parent class's function's scope private in my child class. I have simplified the solution below to illustrate the problem:

class Parent
{
public:
    void doSomething(void* pointer) {}
};

class Child : public Parent
{
public:
    void doSomething(const char* pointer) {}
    
private:
    using Parent::doSomething;
};

int main()
{
    Child childInstance;
    childInstance.doSomething(nullptr);     // error: 'Parent::doSomething': ambiguous call to overloaded function
}

Is this a bug? If not, how do I get around this? I am confused as to why the compiler is even searching in the parent class's namespace, when I have explicitly declared doSomething() as private?

I am using MSVC 2019.

I do NOT want to do either of the following:

  • Rename my subclass function (as this will cause inconsistency for me)
  • Make my inheritance private and manually make public the functions I need (as the inheritance tree is extremely big and this would require making numerous functions in grandparent classes public too and so on, which is unsustainable)
mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Gary Allen
  • 1,218
  • 1
  • 13
  • 28

2 Answers2

2

The simplest approach is just not to have the using directive in the first place.

The inherited function is already hidden because you have a function with the same name. The using directive explicitly un-hides that overload, which actually creates the problem you are trying to solve.

class Child : public Parent
{
public:
    void doSomething(const char* pointer) {}
};

With this definition of Child, the inherited void doSomething(void*); overload is hidden. Even Child foo; foo.doSomething(static_cast<void*>(nullptr)); won't compile!

If you need to call the version on Parent from within Child, you can just qualify the invocation: Parent::doSomething(arg);.

Assuming your problem is specific to nullptr, you could also declare an additional overload that takes std::nullptr_t, which is always the best match for nullptr:

class Child : public Parent
{
public:
    void doSomething(const char* pointer) {}
    void doSomething(std::nullptr_t) {
        doSomething(static_cast<const char*>(nullptr));
    }
    
private:
    using Parent::doSomething;
};
cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • The function parameters are ambiguous - therefore having both of them public in the base class causes a compilation error which was the initial problem – Gary Allen Jun 25 '20 at 18:07
  • And unfortunately my issue was not unique to nullptr. But idclev 463035818's solution works perfectly – Gary Allen Jun 25 '20 at 18:08
  • @GaryAllen Then the code in your question is not in any way representative of the problem you are trying to solve. You talk of "having both of them public in the base class" but your base class only has _one_ version of this function. Please update your code to reflect the question you're asking. – cdhowie Jun 25 '20 at 18:08
  • idclev seemed to have no issue understanding what I was asking. I think it was very clear and obvious I oversimplified the example for demonstration purposes ;) but thanks for helping anyways! – Gary Allen Jun 25 '20 at 18:30
  • @GaryAllen Their answer works with the code in your question as well but it's not the only answer to the code in your question. It sounds more like we both came to the same conclusion about your problem but solved it in a different way. Can you provide a better example of the actual problem? Right now your question won't help future readers as it doesn't seem to illustrate your problem. (If it did, my answer would work.) – cdhowie Jun 25 '20 at 18:51
1

Is this a bug? I am confused as to why the compiler is even searching in the parent class's namespace

The compiler doesn't have to look in the parent class. You explicity told him that you want to use Parent::doSomething;.

Moreover, overload resolution happens before private/public access (cf cppreference):

Member access does not affect visibility: names of private and privately-inherited members are visible and considered by overload resolution, implicit conversions to inaccessible base classes are still considered, etc. Member access check is the last step after any given language construct is interpreted. The intent of this rule is that replacing any private with public never alters the behavior of the program.

Both overloads are an equally good match, hence the ambiguity. Passing a void* should be fine to select the desired overload:

childInstance.doSomething(static_cast<void*>(nullptr));

To have the method from Parent hidden as private and resolve the ambiguity you can add a level of indirection:

class Parent
{
public:
    void doSomething(void* pointer) {}
};

class Intermediate : public  Parent {
   using Parent::doSomething;
};

class Child : public Intermediate
{
public:
    void doSomething(const char* pointer) {}
};

int main()
{
    Child childInstance;
    void* p;
    //childInstance.doSomething(p); // error
    childInstance.doSomething(nullptr); // fine
}

The intermediate ensures that Parent::doSoemthing is private and the call is no longer ambiguous.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Unfortunately the actual situation isn't as simple as explicitly stating the type of the parameter, and also I don't exactly wan't to do that each time. Plus - what if they have the same parameters exactly? – Gary Allen Jun 25 '20 at 16:28
  • @GaryAllen then please clarify your question. Currently it is "Is this a bug?" and thats the question I tried to answer – 463035818_is_not_an_ai Jun 25 '20 at 16:29
  • @GaryAllen took some more edits, but now it should be ok – 463035818_is_not_an_ai Jun 25 '20 at 16:45
  • @GaryAllen I have to admit, I am not 100% sure about the details, what I remember is that name look up stops once it found the name and that is in `Child` already so it wont look any further – 463035818_is_not_an_ai Jun 25 '20 at 18:01
  • Thanks! Yeah, maybe it has something to do with the number of scopes C++ is prepared to look through to resolve the name of the overloads. Nonetheless this works perfectly for my complex scenario as well. It also helps keep the code need as I am explicitly hiding all my functions in one class! – Gary Allen Jun 25 '20 at 18:06
  • @GaryAllen well no, actually the thing about name lookup is nonsense and what the other answer suggests should be fine. Actually I had that before but must have made some stupid mistake that made me believe it isnt fine, but in fact it is – 463035818_is_not_an_ai Jun 25 '20 at 18:13