When you do E1.E2
, you are not talking about a general property of the type of thing that E1
is. You're asking to access a thing within the object designated by E1
, where the name of the thing to be accessed is E2
. If E2
is static, it accesses the class static thing; if E2
is non-static, then it accesses the member thing specific to that object. That's important.
Member variables become the subobject. If your class S
had a non-static data member int i;
, s.i
is a reference to an int
. That reference, from the stand point of an int&
, behaves no differently from any other int&
.
Let me say that more clearly: any int*
or int&
can point to/reference an int
which is a complete object or an int
which is a subobject of some other object. The single construct int&
can serve double-duty in this way.*
Given that understanding of s.i
, what would be the presumed meaning of s.f
? Well, it should be similar, right? s.f
would be some kind of thing that, when called with params
, will be the equivalent of doing s.f(params)
.
But that is not a thing which exists in C++.
There is no language construct in C++ which can represent that meaning of s.f
. Such a construct would need to store a reference to s
as well as the member S::f
.
A function pointer can't do that. Function pointers need to be able to be pointer-interconvertible with void*
**. But such an s.f
would need to store the member S::f
as well as a reference to s
itself. So by definition, it'll have to be bigger than a void*
.
A member pointer can't do that either. Member pointers explicitly don't carry their this
object along with them (that's kind of the point); you must provide them at call-time using the specific member pointer call syntax .*
or .->
.
Oh, there are ways to encode this within the language: lambdas, std::bind
, etc. But there is no language-level construct which has this precise meaning.
Because C++ is asymmetric in this way, where s.i
has an encodable meaning but not s.f
, C++ makes the unencodable one illegal.
You may ask why such a construct doesn't simply get built. It's not really that important. The language works perfectly fine as is, and due to the complexity of what an s.f
would need to be, it's probably best to make you use a lambda (for which admittedly there should be ways to make it shorter to write such things) if that's what you want.
And if you want a naked s.f
to be equivalent to S::f
(ie: designates the member function), that doesn't really work either. First, S::f
doesn't have a type either; the only thing you can do with such a prvalue is convert it to a pointer to a member. Second, a member function pointer doesn't know what object it came from, so in order to use one to call the member, you need to give it s
. Therefore, in a call expression, s
would have to appear twice. Which is really silly.
*: there are things you can do to complete objects that you cannot do to subobjects. But those provoke UB because they're not detectable by the compiler, because an int*
doesn't say if it comes from a subobject or not. Which is the main point; nobody can tell the difference.
**: the standard does not require this, but the standard cannot do something which out-right makes such an implementation impossible. Most implementations provide this functionality, and basically any DLL/SO loading code relies on it. Oh, and it would also be completely incompatible with C, which makes it a non-starter.