4

Does the following lead to undefined behavior as long as the pointer being used to access the pointer-to-member is of a correct type?

And if so, why do I need the cast? It would look a lot nicer without it (and yes, I know that's just a matter of opinion).

struct base { int foo(int base::* ptr) { return this->*ptr; } }; struct sub : base { int blah{ 42 }; }; int main() { return sub{}.foo(static_cast<int base::*>(&sub::blah)); } 
2
  • 1
    I don't know about the formal here, but it's very much not type safe, you can call foo on an object that doesn't have the int member, so just don't do that. Put another way, knowing that it's not formally UB, if it isn't, doesn't help with the real problem, the type unsafety, so the UB or not is pretty irrelevant other than as a language lawyer question (but if that's your interest then consider tagging the question as such). What is the problem you're trying to solve this way? Commented Sep 13, 2017 at 2:31
  • I'm not trying to solve a problem, yet. I've recently started working with some code that uses it extensively and the pattern just doesn't sit well with me at all. Commented Sep 13, 2017 at 2:37

1 Answer 1

6

Does the following lead to undefined behavior as long as the pointer being used to access the pointer-to-member is of a correct type?

No, it's well formed. The rule, from [expr.mptr.oper] is:

If the dynamic type of E1 does not contain the member to which E2 refers, the behavior is undefined.

The dynamic type of *this is sub, which does contain the member, so this is fine.

And if so, why do I need the cast?

Because it's an inherently unsafe cast, and the rule of thumb is that inherently unsafe operations should be loud and visible. In this specific case, it's fine, but that's only because you were being careful. Requiring the cast forces you to have to think about it.

A simpler example might be to look at just the pointers instead of the pointer-to-member. In a simple hierarchy (assuming public, non-ambiguous, etc.), it is always safe to cast a Derived* to a Base*. There's nothing problematic there, so you don't need to write a cast. However, it is not always safe to cast a Base* to a Derived*... you might not have a Derived* there. But, it's not never safe - disallowing that cast entirely would be bad. So the safe cast is implicit, but the unsafe cast must be explicit.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks, I'll have to just argue for the inherently unsafeness of it all to get it changed, since I can't use the UB argument.
@evan Huh? To get what changed?
not the standard. The code that I've recently started working with.
@evan It's not unsafe if you're careful when using it. You might instead consider having a template member function that ensures that the current object can be dynamic_casted to the object type of the member pointer and asserts on failure, then calls the real foo() -- and you can disable this assertion in the release build.
@evan Here's an example of what I mean. Note that base needs to be polymorphic for this approach to work. This will at least cause a loud failure when an incorrect pointer is passed in, instead of UB.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.