c++overloadingimplicit-conversionvoid-pointerspointer-conversion

C++ polymorphism function taking void * and other pointer type as argument: is it considered ambiguous?


C++ polymorphism functions taking void * and other pointer type as their arguments: is it considered ambiguous?

I am worried that since any pointer can be cast to void*, will the 2nd call of bar below executes void bar(void*) instead of my expected void bar(int*), in the program below?

I tested on my g++, and it runs as expected (i.e. int* won't be cast to void*). But can anyone comment/answer on/to this question in the respect of C++ language specifications?

foo.h:

class Foo {
public:
    void bar(void *);
    void bar(int *);
};

main.cpp:

...
struct A *p1;
int *p2;
Foo foo;
...
foo.bar(p1);
foo.bar(p2);

Furthermore, say, bar are now virtual polymorphism functions, taking void* argument as the 1st form, and base abstract class pointer argument as the 2nd form. Will a call with a derived class pointer as the argument execute the 1st form or the 2nd form? i.e. Will the derived class pointer been cast to its base abstract class pointer (and thus the 2nd form will be in action), or will it be cast to void * (and thus the 1st form will be in action) before calling bar()?


Solution

  • According to overload resolution rules (section Ranking of implicit conversion sequences), as the argument can be converted to either function's parameter type, the best viable function in this case would be the one whose implicit conversion is better.

    For:

    class Foo {
      public:
        void bar(void*);
        void bar(int*);
    };
    
    // ...
    
    Foo foo;
    int* p2;
    foo.bar(p2);
    

    The first is rank 3 (Conversion), while the second is rank 1 (Exact Match). As an exact match, which requires no conversion, is better than a conversion, it will call void bar(int*).

    It gets more complex in your second case, however:

    class Foo {
      public:
        virtual void bar(void*);
        virtual void bar(Foo*);
        virtual ~Foo() = default;
    };
    
    class FooTwo : public Foo {};
    
    // ...
    
    Foo foo;
    FooTwo footwo;
    
    foo.bar(&footwo);
    

    As both are rank 3 (Conversion), this then follows conversion ranking rules. And as both conversions have the same conversion rank, this then goes to extended conversion ranking rules. Extended rule 2 states:

    Conversion that converts pointer-to-derived to pointer-to-base is better than the conversion of pointer-to-derived to pointer-to-void, and conversion of pointer-to-base to void is better than pointer-to-derived to void.

    Considering this, void bar(Foo*) is considered a better match than void bar(void*), meaning that it will be selected by foo.bar(&footwo);.

    See here for an example of the latter.