Overload Resolution and Virtual Methods

Overload resolution and virtual methods

The answer is in the C# specification section 7.3 and section 7.5.5.1

I broke down the steps used for choosing the method to invoke.

  • First, the set of all accessible members named N (N=Foo) declared in T (T=class D) and the base types of T (class C) is constructed. Declarations that include an override modifier are excluded from the set (D.Foo(B) is exclude)

    S = { C.Foo(B) ; D.Foo(A) }
  • The set of candidate methods for the method invocation is constructed. Starting with the set of methods associated with M, which were found by the previous member lookup, the set is reduced to those methods that are applicable with respect to the argument list AL (AL=B). The set reduction consists of applying the following rules to each method T.N in the set, where T (T=class D) is the type in which the method N (N=Foo) is declared:

    • If N is not applicable with respect to AL (Section 7.4.2.1), then N is removed from the set.

      • C.Foo(B) is applicable with respect to AL
      • D.Foo(A) is applicable with respect to AL

        S = { C.Foo(B) ; D.Foo(A) }
    • If N is applicable with respect to AL (Section 7.4.2.1), then all methods declared in a base type of T are removed from the set. C.Foo(B) is removed from the set

          S = { D.Foo(A) }

At the end the winner is D.Foo(A).


If the abstract method is removed from C

If the abstract method is removed from C, the initial set is S = { D.Foo(B) ; D.Foo(A) } and the overload resolution rule must be used to select the best function member in that set.

In this case the winner is D.Foo(B).

Overload resolution of virtual methods

This is by design. Section 7.5.3 of the C# language specification states:

For example, the set of candidates for a method invocation does not include methods marked override (§7.4), and methods in a base class are not candidates if any method in a derived class is applicable (§7.6.5.1).

In other words, because your Derived class has a non-overridden Add method, the Add method in the Base class (and its overridden version in Derived) are no longer candidates for overload resolution.

Even though Base.Add(int,int) would be a better match, the existance of Derived.Add(float,float) means that the base class method is never even considered by the compiler.

Eric Lippert discusses some of the reasons for this design in this blog post.

Overloaded virtual function call resolution

The choice of which version of f to call is made by looking at the compile-time type of the parameter. The run-time type isn't considered for this name resolution. Since b1 is of type Bbase*, all of Bbase's members are considered; the one that takes an A2* is the best match, so that's the one that gets called.

overloading virtual function and calling derived function by pointer to base class

Overload resolution uses the static types (not that it would make a
difference here). So both b->function() and b->function( s )
resolve to Base::function, with no error. Finally, since these
functions have been declared virtual, the final resolution will take
into account any overloads in a derived class. But there aren't any, so
the function in the base class will be called.

Name hiding occurs during name lookup, which is before overload
resolution, and also only concerns the static type; in an expression
like b->function() or b->function( s ), the compiler ignores
Derived completely; it does the name lookup on the static type. Name
hiding would only come into consideration is the static type were
Derived; once the compiler found function in Derived, it would
look no further.

The global rules are fairly simple: name lookup (using static type),
then overload resolution (using static type), and finally, if the
resolved name is a virtual function, dynamic determination of the actual
function depending on the dynamic type.

Can I overload pure virtual method in the base class?

This is how derived class member lookup works: in the expression b.fun(), fun is first looked up in the scope of class B, and the lookup finds B::fun(int). So it stops and never finds A::fun().

Relevant section of the standard is 10.2 [class.member.lookup]/4:

If C contains a declaration of the name f, the declaration set contains every declaration of f declared in
C that satisfies the requirements of the language construct in which the lookup occurs. (...) If the resulting declaration set is not empty, the subobject set contains C itself, and calculation is complete.

To make the base class function directly accessible you can use a using declaration in the derived class, i.e. using A::fun;.

For methods that are implemented in the base class an alternative is sometimes to qualify to call, i.e. b.A::fun().

How does overloading a Virtual method differ from a Non-Virtual method?

When you have a Base class method declared as virtual, In order to override it you need to provide an function with exact same signature in Derived class(Co-variant return types are allowed though).

If your function name is same but the signature in Derived class varies from one in Base class than it is not overidding anymore, It is function Hiding, the derived class method hides the Base class method.

Function Overloading is never accross classes, You can overload methods inside the same class or free functions but not accross classes. When you attempt to do it accross classes what you eventually get is function hiding.

To bring the Base class methods in scope of your Derived class you need to add an

additional using functionName, to your Derived class.

EDIT:
As for the Q of when to use virtual over overloading,the answer is:

If you intend functions of your class to be overridden for runtime polymorphism you should mark them as virtual, and not if you don't intend so.

Good Read:

When to mark a function in C++ as a virtual?

Resolving virtual method overloads across base classes

This is indeed ambiguity according to the standard, but you can use using or specify the base class explicitly:

class join_1_2 : public virtual base_1, public virtual base_2
{
public:
using base_1::foo;
using base_2::foo;
};

void sink(join_1_2 ¶m)
{
param.base_2::foo(42, 3.14);
}

7.3.3 The using declaration

For the purpose of overload resolution, the functions which are introduced by a using-declaration into a
derived class will be treated as though they were members of the derived class.

Overload resolution for pointer to derived class

Without virtual dispatch, overloaded resolution is performed at compile-time, i.e. based on the static type of objects, not the dynamic type. Since ab2 is a std::shared_ptr<A>, the A overload is chosen.

To involve run-time type information (i.e. the dynamic type of the object), you must use virtual methods (and polymorphism), not overload resolution. It's not clear how to best incorporate this into the given example.

The most straightforward way is giving A a virtual destructor and some virtual method like accept() that is overridden in derived classes to dispatch to the most appropriate overload of the free function explicitly. There are other ways though, it depends on what you need.

It should also be noted that there is no SFINAE whatsoever in this code.


To reiterate and respond to the edit: If you want to make use of dynamic instead of static type information, your type must have at least one virtual method:

class A { virtual ~A(); };

If you do that, then you can use e.g. dynamic_cast (or, since you are using std::shared_ptr, std::dynamic_pointer_cast) to determine the run-time type of an object:

void accept(std::shared_ptr<B1> sp)  { std::cout << "B1 "; }

void accept(std::shared_ptr<B2> sp) { std::cout << "B2 "; }

// How to ensure the overload call to right derived type ?
void accept(std::shared_ptr<A> sp) {
if (std::shared_ptr<B1> ptrB1 = std::dynamic_pointer_cast<B1>(sp))
accept(ptrB1);
else if (std::shared_ptr<B2> ptrB2 = std::dynamic_pointer_cast<B2>(sp))
accept(ptrB2);
else
// if is A, do another thing
}

But if you manually dynamic_cast between different types like this, then you probably don't have the right abstraction. Look into variants or the visitor pattern (or ask for this advice in a new question where you detail the problem and constraints you have).



Related Topics



Leave a reply



Submit