2

Consider the following code:

int main()
{
    struct EmptyStruct{
        void nonstatic_mf() const { std::cout <<"EmptyStruct\n"; }
    };


    EmptyStruct *esptr = nullptr;
    esptr->nonstatic_mf(); 
}  

Is this a legal C++ (it does seem to work in gcc and clang)?

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
Adrian
  • 987
  • 5
  • 13
  • 1
    More interesting question is for static methods :-) – Jarod42 Jan 31 '20 at 16:14
  • 1
    Adding in code like `if (this != nullptr) ...` don't end well either, because for optimized code the compiler knows that `this` can never be nullptr, so the code is as-if `if (true) ...`. Undefined behavior: bad. Turn on all compiler warnings is one way to have a fighting chance against undefined behavior. – Eljay Jan 31 '20 at 16:30
  • 1
    @Jarod42 do you want me to ask it? – YSC Jan 31 '20 at 16:50
  • 1
    because simply answering [`[expr.post]/2`](http://eel.is/c++draft/expr.post#expr.ref-2.sentence-3) isn't that fun... – YSC Jan 31 '20 at 16:52
  • @YSC you have actually answered my question! Thanks a lot – Adrian Jan 31 '20 at 17:03
  • @Adrian tip: if you're looking for an authoritative answer, tag your question with language-lawyer. We'll know what you're searching for. – YSC Jan 31 '20 at 17:09

1 Answers1

8

Even though the struct is empty, it has a non zero size. There must be some memory that act as a storage for it.

No. This is always UB. Make it static if you don't need the instance. Static functions are still callable using the dot . syntax.

Why? Because you cannot dereference a null pointer. Calling a member function equivalent to this:

 EmptyStruct *esptr = nullptr;
 (*esptr).nonstatic_mf();

As you can see, the null pointer is deferenced, which is UB.

What does the standard says about this? From [class.mfct.non-static]/2:

If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.

A null pointer don't point to a valid instance of EmptyStruct. This alone is enough to make the behaviour undefined

And from [expr.ref]/2:

For the first option (dot) the first expression shall be a glvalue. For the second option (arrow) the first expression shall be a prvalue having pointer type. The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of [expr.ref] will address only the first option (dot).

So esptr->nonstatic_mf() is effectively equivalent to (*esptr).nonstatic_mf(), and derefencing a null pointer is undefined behaviour.

So there's two way in which this code is undefined.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • I guess the ``more or less" here is the crucial part. I would naively say that the argument of a member function is a pointer (no need to dereference anything), but I don't now what standard says about it, so I'm asking. – Adrian Jan 31 '20 at 16:29
  • OK, thanks, I'm marking this as the answer. I was just thinking that perhaps member functions are sort of called by pointer (rather than reference) and built-in arrow is more fundamental than dot (even though dot cannot be overloaded). But it's the other way around. – Adrian Jan 31 '20 at 18:11
  • 1
    @Adrian yes. `this` should have been a reference from the start since it cannot in any well formed way be null. Compilers will even remove checks such as `if (this == nullptr)` when optimisations are enabled. – Guillaume Racicot Jan 31 '20 at 18:12