Consider this code:
struct A
{
void foo() const
{
std::cout << \"const\" << std::endl;
}
private:
void foo()
The technical reason has been answered by other answers. I'll only focus on this question:
In other words why overload resolution comes before access control? This is strange. Do you think it is consistent? My code works and then I add a method and my working code does not compile at all.
That's how the language was designed. The intent is trying to call the best viable overload, as far as possible. If it fails, an error will be triggered to remind you to consider the design again.
On the other hand, suppose your code compiled and worked well with the const
member function being invoked. Someday, someone (maybe yourself) then decides to change the accessibility of the non-const
member function from private
to public
. Then, the behavior would change without any compile errors! This would be a surprise.
Access controls (public
, protected
, private
) do not affect overload resolution. The compiler chooses void foo()
because it's the best match. The fact that it's not accessible doesn't change that. Removing it leaves only void foo() const
, which is then the best (i.e., only) match.
Suppose access control came before overload resolution. Effectively, this would mean that public/protected/private
controlled visibility rather than accessibility.
Section 2.10 of Design and Evolution of C++ by Stroustrup has a passage on this where he discusses the following example
int a; // global a
class X {
private:
int a; // member X::a
};
class XX : public X {
void f() { a = 1; } // which a?
};
Stroustrup mentions that a benefit of the current rules (visibility before accessibility) is that (temporarily) chaning the private
inside class X
into public
(e.g. for the purposes of debugging) is that there is no quiet change in the meaning of the above program (i.e. X::a
is attempted to be accessed in both cases, which gives an access error in the above example). If public/protected/private
would control visibility, the meaning of the program would change (global a
would be called with private
, otherwise X::a
).
He then states that he does not recall whether it was by explicit design or a side effect of the preprocessor technology used to implement the C with Classess predecessor to Standard C++.
How is this related to your example? Basically because the Standard made overload resolution conform to the general rule that name lookup comes before access control.
10.2 Member name lookup [class.member.lookup]
1 Member name lookup determines the meaning of a name (id-expression) in a class scope (3.3.7). Name lookup can result in an ambiguity, in which case the program is ill-formed. For an id-expression, name lookup begins in the class scope of this; for a qualified-id, name lookup begins in the scope of the nestedname- specifier. Name lookup takes place before access control (3.4, Clause 11).
8 If the name of an overloaded function is unambiguously found, overloading resolution (13.3) also takes place before access control. Ambiguities can often be resolved by qualifying a name with its class name.
It's important to keep in mind the order of things that happen, which is:
delete
d), fail. (3) happens after (2). Which is really important, because otherwise making functions delete
d or private
would become sort of meaningless and much harder to reason about.
In this case:
A::foo()
and A::foo() const
.A::foo()
because the latter involves a qualification conversion on the implicit this
argument.A::foo()
is private
and you don't have access to it, hence the code is ill-formed. Because the variable a
in the main
function is not declared as const
.
Constant member functions are called on constant objects.
This comes down to a fairly basic design decision in C++.
When looking up the function to satisfy a call, the compiler carries out a search like this:
It searches to find the first1 scope at which there's something with that name.
The compiler finds all the functions (or functors, etc.) with that name in that scope.
Then the compiler does overload resolution to find the best candidate among those it found (whether they're accessible or not).
Finally, the compiler checks whether that chosen function is accessible.
Because of that ordering, yes, it's possible that the compiler will choose an overload that's not accessible, even though there's another overload that's accessible (but not chosen during overload resolution).
As to whether it would be possible to do things differently: yes, it's undoubtedly possible. It would definitely lead to quite a different language than C++ though. It turns out that a lot of seemingly rather minor decisions can have ramifications that affect a lot more than might be initially obvious.