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:
- To promote
m
(which is non-const
) toconst
and call theint
overload (gcc issues a warning but does that); - To try and construct a temporary
InArgClass
instance from100
and call theInArgClass 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:
- Mark the constructor as explicit
class InArgClass
{
explicit InArgClass(int In) { }
};
- Mark the first overload as
const
(if possible)
void TestFunc(const InArgClass& Name) const { }
- Make
m
aToolClass 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.