8

This seems to compile and access the private data successfully. Is this well-defined behavior?

#include <iostream>
#include <string>

using std::string;

class foo {
    string private_data = "Hello World";
};

int main()
{
    foo f;
    auto* pprivate_data = reinterpret_cast<string*>(&f);
    std::cout << *pprivate_data << '\n';
}

This question is sort of similar, but I believe it doesn't address my question.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • It should be as long as foo is POD. – Kostas Jul 27 '20 at 09:50
  • How could it possibly not work 100% of the time? Why would you ever use the `class`-keyword anyways? –  Jul 27 '20 at 09:50
  • 1
    @super of course it works fine in practise. I was wondering if it is technically well defined as you can tell by the language-lawyer tag – Aykhan Hagverdili Jul 27 '20 at 09:53
  • When would this ever happen? –  Jul 27 '20 at 09:56
  • If `foo` contains more than one member, I'm almost sure that there will be a strict aliasing rule violation for `reinterpret_cast`ing a complete `foo` instance into one of its member type. – Fareanor Jul 27 '20 at 09:59
  • 4
    I suppose the downvoter misses the point that language lawyer questions are not necessarily about code that one would actually write, because I don't see anything wrong with the question – 463035818_is_not_an_ai Jul 27 '20 at 09:59

2 Answers2

9

No, the behavior is undefined. For such a reintepret_cast to have meaning, the two objects must be interconvertible

[basic.compound]

4 Two objects a and b are pointer-interconvertible if:

  • they are the same object, or
  • one is a union object and the other is a non-static data member of that object ([class.union]), or
  • one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, any base class subobject of that object ([class.mem]), or
  • there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_­cast. [ Note: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note ]

The only bullet that might apply is the one about standard layout classes. If we consult that definition, we see

[class.prop]

3 A class S is a standard-layout class if it:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • [...]

there is an immediate problem. Any non-static data members of the object must be standard layout themselves. There is no guarantee std::string is a standard layout type. So the behavior is undefined.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Gotta love undefined behaviour! –  Jul 27 '20 at 09:59
  • What if it was `class foo { chat const* private_data = "Hello World"; };` and we tried to access that through `reinterpret_cast`? – Aykhan Hagverdili Jul 27 '20 at 10:04
  • 1
    @Ayxan - Then if we consult the list in **[class.prop]**, `foo` is standard layout. So the conversion becomes well-defined. – StoryTeller - Unslander Monica Jul 27 '20 at 10:05
  • What about strict aliasing rule violation? Isn't [basic.lval/11](http://eel.is/c++draft/basic.lval#11) enough to state UB? – Evg Jul 27 '20 at 10:07
  • 1
    @Evg - That is irrelevant. You can always modify `private_data` via a pointer that is obtained legally. It doesn't matter that its a sub-object of `f`. The question here is if the way the pointer is obtained is legal. – StoryTeller - Unslander Monica Jul 27 '20 at 10:09
  • Perhaps I'm wrong but I think @Evg wanted to say that _"the way the pointer is obtained"_ may break the strict aliasing rule (since we try to `reinterpret_cast` a `foo*` into a `std::string*`) and thus making it illegal. – Fareanor Jul 27 '20 at 10:34
  • @Fareanor - The strict aliasing rule doesn't care about how the pointer is obtained. And the reintpert_cast itself is not UB on its own. Using the pointer for *access* is what **may** trip strict aliasing. Or are you saying a class cannot give out pointers to its members to other function to modify? – StoryTeller - Unslander Monica Jul 27 '20 at 10:36
  • It looks like we're trying to access `foo` via `std::string*` when we do `*pprivate_data`. – Evg Jul 27 '20 at 10:40
  • @Evg - No, we are accessing `f.private_data`. – StoryTeller - Unslander Monica Jul 27 '20 at 10:41
  • You're right, strict aliasing may trigger only when we try to access it. But what would be the meaning of getting such a pointer if it's not to use it at some point ? And i'm not saying a class cannot provide pointers to its data member, but this is not the case here, we are trying to convert the class instance pointer to one of its members pointer. – Fareanor Jul 27 '20 at 10:41
  • 1
    @Fareanor - Yes, we are trying to convert here. And the standard says we can get the correct address, same as if `&f.private_data` was used to obtain the address. – StoryTeller - Unslander Monica Jul 27 '20 at 10:43
  • Ok there is something I didn't understand then. For me @Evg is right. This is `&f` not `&f.private_data` that was given to `reinterpret_cast` parameter. – Fareanor Jul 27 '20 at 10:44
  • Oh alright, I didn't know that. Thanks for the explanation. – Fareanor Jul 27 '20 at 10:45
  • Of course you could `static_assert(std::is_standard_layout_v)` to restrict yourself to implementations where this is defined behaviour – Caleth Jul 27 '20 at 13:29
8

Yes, this is fine on condition std::string (and thus class foo) is standard-layout (it is in libstdc++, libc++ and MSVC STL). Per class.mem/26 :

If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member [...] [Note: The object and its first subobject are pointer-interconvertible ([basic.compound], [expr.static.cast]). — end note]

And basic.compund/4:

Two objects a and b are pointer-interconvertible if: [...]

  • one is a standard-layout class object and the other is the first non-static data member of that object [...]

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_­cast.

Obviously, this only works for the first non-static data member.

ecatmur
  • 152,476
  • 27
  • 293
  • 366