ChatGPT解决这个技术问题 Extra ChatGPT

Why is a public const method not called when the non-const one is private?

Consider this code:

struct A
{
    void foo() const
    {
        std::cout << "const" << std::endl;
    }

    private:

        void foo()
        {
            std::cout << "non - const" << std::endl;
        }
};

int main()
{
    A a;
    a.foo();
}

The compiler error is:

error: 'void A::foo()' is private`.

But when I delete the private one it just works. Why is the public const method not called when the non-const one is private?

In other words, why does overload resolution come 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.

In C++, without extra effort like use of PIMPL idiom, there is no real "private" part of the class. This is just one of the problems (adding a "private" method overload and breaking compilation old code counts as a problem in my book, even if this one is trivial to avoid by just not doing it) caused by it.
Is there any real-life code where you would expect to be able to call a const function but that its non-const counterpart would be part of the private interface ? This sounds like bad interface design to me.

P
Peter Mortensen

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.


Is there any reason why access control is after overload resolution?
@drake7707 Wll as I show in my code sample you if access control came first then the above code would compile, which changes the semantics of the program. Not sure about you but I would rather have an error and need to do an explicit cast if I wanted the function to stay private then a implicit cast and the code silently "works".
"and need to do an explicit cast if I wanted the function to stay private" - it sounds like the real issue here is implicit casts... although on the other hand, the idea that you can also use a derived class implicitly as the base class is a defining characteristic of OO paradigm, isn't it?
a
atkins

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:

It prevents unexpected changes of behaviour as a result of changing the accessibility of an overload candidate (instead, a compile error will occur). It removes context-dependence from the overload resolution process (i.e. overload resolution would have the same result whether inside or outside the class).


T
TemplateRex

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.


B
Bathsheba

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.


Do you think it is consistent? My code works and then I add a method and my working code does not compile at all.
I do think so. Overload resolution is intentionally fussy. I answered a similar question yesterday: stackoverflow.com/questions/39023325/…
@Narek I believe it works just like how deleted functions do in overload resolution. It picks the best one from the set and then it sees it is not available so you get a compiler error. It doesn't pick the best usable function but the best function and then tries to use it.
@Narek I also first wondered why it doesnt work, but consider this: how would you ever call the private function if the public const one should be chosen also for non const objects?
B
Barry

It's important to keep in mind the order of things that happen, which is:

Find all the viable functions. Pick the best viable function. If there isn't exactly one best viable, or if you can't actually call the best viable function (due to access violations or the function being deleted), fail.

(3) happens after (2). Which is really important, because otherwise making functions deleted or private would become sort of meaningless and much harder to reason about.

In this case:

The viable functions are A::foo() and A::foo() const. The best viable function is A::foo() because the latter involves a qualification conversion on the implicit this argument. But A::foo() is private and you don't have access to it, hence the code is ill-formed.


One might think "viable" would include relevant access restrictions. In other words it's not "viable" to call a private function from outside the class, as it's not part of the public interface of that class.
J
Jerry Coffin

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.

"First" can be a little complex in itself, especially when/if templates get involved, since they can lead to two-phase lookup, meaning there are two entirely separate "roots" to start from when doing the search. The basic idea is pretty simple though: start from the smallest enclosing scope, and work your way outward to larger and larger enclosing scopes.


Stroustrup speculates in D&E that the rule might be a side effect of the preprocessor used in C with Classes that never got reviewed once more advanced compiler technology became available. See my answer.
P
Pete Becker

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.


W
WhiZTiM

In this call:

a.foo();

There is always an implicit this pointer available in every member function. And the const qualification of this is taken from the calling reference/object. The above call is treated by the compiler as:

A::foo(a);

But you have two declarations of A::foo which is treated like:

A::foo(A* );
A::foo(A const* );

By overload resolution, the first will be selected for non-const this, the second will be selected for a const this. If you remove the first, the second will bind to both const and non-const this.

After overload resolution to select the best viable function, comes access control. Since you specified access to the chosen overload as private, the compiler will then complain.

The standard says so:

[class.access/4]: ...In the case of overloaded function names, access control is applied to the function selected by overload resolution....

But if you do this:

A a;
const A& ac = a;
ac.foo();

Then, only the const overload will be fit.


That is STRANGE that After overload resolution to select the best viable function, comes access control. Access control should come before overload resolution as if you con't have access should you should not consider it at all, what you think?
@Narek ,.. I've updated my answer with a reference to the C++ standard. It actually makes sense that way, there are a lot of things and idioms in C++ that depends on this behavior
C
Cody Gray

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.


S
Some programmer dude

Because the variable a in the main function is not declared as const.

Constant member functions are called on constant objects.


K
Kyle Strand

Access specifiers do not affect name-lookup and function-call resolution, ever. The function is selected before the compiler checks whether the call should trigger an access violation.

This way, if you change an access specifier, you'll be alerted at compile-time if there is a violation in existing code; if privacy were taken into account for function call resolution, your program's behavior could silently change.