If the function is non-constant, then the function is allowed to change values of the object on which it is being called. So the compiler doesn't allow to create this chance and prevent you to call a non-constant function on a constant object, as constant object means you cannot change anything of it anymore.
const member functions may be invoked for const and non-const objects. non-const member functions can only be invoked for non-const objects. If a non-const member function is invoked on a const object, it is a compiler error.
How to call a non-const method from a const method? You should not. You might run into undefined behaviour if you cast away the const-ness of this , using const_cast . The usage of const_cast will shut the compiler's mouth up, but that isn't a solution.
Do not return non-const handles to Class data from const member Functions. From a language point of view, the pointer 'p' is part of the class and then cannot be modified in a 'const' function. But the pointed-to value is not part of the class, and may be modified.
When you call a.foo();
, the compiler goes through overload resolution to find the best function to use. When it builds the overload set it finds
void foo() const
and
void foo()
Now, since a
is not const
, the non-const version is the best match, so the compiler picks void foo()
. Then the access restrictions are put in place and you get a compiler error, since void foo()
is private.
Remember, in overload resolution it is not 'find the best usable function'. It is 'find the best function and try to use it'. If it can't because of access restrictions or being deleted, then you get a compiler error.
In other words why does overload resolution comes before access control?
Well, let's look at:
struct Base
{
void foo() { std::cout << "Base\n"; }
};
struct Derived : Base
{
void foo() { std::cout << "Derived\n"; }
};
struct Foo
{
void foo(Base * b) { b->foo(); }
private:
void foo(Derived * d) { d->foo(); }
};
int main()
{
Derived d;
Foo f;
f.foo(&d);
}
Now let's say that I did not actually mean to make void foo(Derived * d)
private. If access control came first then this program would compile and run and Base
would be printed. This could be very hard to track down in a large code base. Since access control comes after overload resolution I get a nice compiler error telling me the function I want it to call cannot be called, and I can find the bug a lot easier.
Ultimately this comes down to the assertion in the standard that accessibility should not be taken into consideration when performing overload resolution. This assertion may be found in [over.match] clause 3:
... When overload resolution succeeds, and the best viable function is not accessible (Clause [class.access]) in the context in which it is used, the program is ill-formed.
and also the Note in clause 1 of the same section:
[ Note: The function selected by overload resolution is not guaranteed to be appropriate for the context. Other restrictions, such as the accessibility of the function, can make its use in the calling context ill-formed. — end note ]
As for why, I can think of a couple of possible motivations:
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.
Since the implicit this
pointer is non-const
, the compiler will first check for the presence of a non-const
version of the function before a const
version.
If you explicitly mark the non-const
one private
then the resolution will fail, and the compiler will not continue searching.
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. 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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With