Home > OS >  Why does a method with the same type argument with "const" at the end give an ERROR 0308?
Why does a method with the same type argument with "const" at the end give an ERROR 0308?

Time:04-14

code:

class InArgClass
{
    InArgClass(int In) { }
};

class ToolClass
{
public:
    void TestFunc(const InArgClass& Name) { }

    void TestFunc(int index) const { } // E0308
    //void TestFunc(int index) { }; // NO ERROR
};

int main()
{
    //std::cout << "Hello World!\n";
    ToolClass* m = new ToolClass();
    m->TestFunc(100);
}

The class has a method with int argument, so in "const" at end of function int and InArgClass equal?

ERROR 0308: more than one instance of overloaded function

CodePudding user response:

So, here you are the problem. InArgClass defines a (private) constructor from an int which is not marked explicit.

When you try to call m->TestFunc(100), the compiler has to decide whether:

  1. To promote m (which is non-const) to const and call the int overload (gcc issues a warning but does that);
  2. To try and construct a temporary InArgClass instance from 100 and call the InArgClass const& overload (it will later realize that it cannot as the constructor is private).

There are at least 3 ways to make that call non ambiguous:

  1. Mark the constructor as explicit
class InArgClass
{
    explicit InArgClass(int In) { }
};
  1. Mark the first overload as const (if possible)
void TestFunc(const InArgClass& Name) const { }
  1. Make m a ToolClass const*:
ToolClass const* m = new ToolClass();

CodePudding user response:

The problem appears because the overload resolution considers whether implicit conversions needs to be applied.

No conversions is of course being the preferred.

So in your example

ToolClass* m = new ToolClass();
m->TestFunc(100)

It sees a to const conversion, and a conversion via the int constructor of InArgClass.

These conversions has the same cost, so the compiler says it is ambiguous.

So if you for instance change this to

const ToolClass* m = new ToolClass();
m->TestFunc(100);

There is no longer a const conversion. So the const version of TestFunc() is the no-conversion form, and will be selected.

And the [NO ERROR] version also is a no-conversion alternative.

And finally, as @paolo noted, this resolution is done before visibility considerations (for good reason).

CodePudding user response:

Case 1

Here we consider the case where we have the following 2 member functions:

void TestFunc(const InArgClass& Name) { }

void TestFunc(int index) const { } //ERROR 

Now, for the call expression m->TestFunc(100);, during overload resolution both of the above shown member functions are treatd as if they have an implicit object parameter. In case of the member function ToolClass::TestFunc(const InArgClass&) the implicit object parameter has type ToolClass&. While the 2nd member function ToolClass::TestFunc(int index) const has an implicit object parameter of type const ToolClass& during overload resolution.

Now for the call expression, m->TestFunc(100), both of the above mentioned member functions are viable.

But note that since m point to a non-const ToolClass object and so for the 1st member function there is no need for any conversion for the first argument(since the implicit object parameter is of type ToolClass&) but an implicit conversion is needed for the second argument from int to const InArgClass type object.

On the other hand, in case of the second member function, since the implicit object parameter is of type const ToolClass&, so a conversion is needed for the first passed argument. But no conversion is needed for the second passed argument.

Thus, both of these member functions are equally ranked(meaning they are equally worse). And hence the mentioned ambiguous error.

Case 2

Here we consider the case where we've the following 2 member functions:

void TestFunc(const InArgClass& Name) { }
void TestFunc(int index) { };

In this case the second member function ToolClass::TestFunc(int index) is treated as if it has an implicit object parameter of type ToolClass& during overloaded resolution.

This means that this time, for the call expression m->TestFunc(100) there is no implicit conversion needed for the first passed argument to the implicit object parameter since the object on which the member function was called is essentially a non-const ToolClass and the implied object parameter has the type ToolClass& instead of const ToolClass&. Thus, this time the 2nd member function is a better match than the first member function as for the first member function there is need for an implicit conversion of the second passed int argument. So, the second member function is a better match than the first member function in this case and hence this compiles fine.


One way to solve this(the error in case 1) is to make the converting ctor inside class InArgClass explicit as shown below:

class InArgClass
{
   explicit InArgClass(int In) { }
};

CodePudding user response:

When overload resolution is performed against the two overloads of the member function, they are considered to have an additional parameter, called the implicit object parameter. The argument to this parameter is the object expression (here *m).

For a const-qualified member function, since it should be callable also with a const-qualified object expression, the implicit object parameter will have type const ToolClass&.

If the function is not const-qualified, then it will be ToolClass& instead, since it shouldn't be callable on a const glvalue.

So, essentially the two function's parameter lists against which overload resolution is performed are

TestFunc(ToolClass&, const InArgClass& Name)
TestFunc(const ToolClass&, int Name)

and the arguments are a non-const ToolClass lvalue (*m) and an int prvalue (100).

Both functions are viable: Both ToolClass& and const ToolClass& can bind directly to a non-const lvalue of type ToolClass. 100 can be passed in the second overload without conversion and in the first overload with a user-defined conversion to InArgClass using its converting (non-explicit) constructor and the const lvalue reference parameter can bind to the resulting temporary. That constructor is private, so not accessible in the context, but overload resolution does not consider accessibility. It would be checked later if that overload were to be chosen.

The conversion from int to InArgClass is clearly worse than the identity conversion from int to int. So the second overload would be preferred based on the second argument/parameter pair.

However, for the first argument, binding to a const lvalue reference is considered better than binding to a non-const lvalue reference. So based on the first argument/parameter pair, the first overload should be preferred.

If two argument/parameter pairs disagree on being a better overload in this way, then neither function is considered better than the other and since there is no third overload to consider, the whole overload resolution will end up ambiguous.

Without const on the second overload, the first parameter/argument pair will not make either overload better than the other, but since the second pair is a better match for the second overload, the second overload will be chosen in the end.

  •  Tags:  
  • c
  • Related