1

It it undefined behavior to cast an unrelated type to an empty base class? And then use that address to construct a derived type that inherits from that empty base? For example

class Derived : public EmptyBase {
public:
    template <typename T>
    Derived(T&& t) : EmptyBase{std::forward<T>(t)} {}
    using EmptyBase::print;
};

and then is it undefined to do something like this

static auto shim = 1;
auto derived = Derived{*reinterpret_cast<EmptyBase*>(&shim)};
derived.print();

The standard guarantees that empty bases must be optimized away in a standard layout struct, but not sure about whether something like this is allowed to construct a derived class

Curious
  • 20,870
  • 8
  • 61
  • 146
  • 3
    This code makes no sense at all to me. What are you trying to accomplish? – Mark Ransom Jan 16 '18 at 02:47
  • @MarkRansom I don't think that is an appropriate question to ask when I use the language-lawyer tag. You should believe that the person asking the question needs such a thing in a niche situation that might possibly be hard to explain in a stack overflow post, especially given that they have demonstrated having tried to do research around the topic. Moreover, several times explaining use-cases in detail with something like this becomes tangential to the post. Which part does not make sense to you? – Curious Jan 16 '18 at 04:29
  • @Curious I meant what I said - I'm having a hard time understanding the code. If I can't understand it I can't answer it. Just a little context might go a long way. – Mark Ransom Jan 16 '18 at 04:51

1 Answers1

4
Derived{*reinterpret_cast<EmptyBase*>(&shim)}

Derived's constructor accesses the value representation of its parameter to initialize its base class. The parameter has the type EmptyBase&&. But there is no EmptyBase object at that address; there is an int at that address.

Therefore, you are accessing the value representation of an int through a glvalue (EmptyBase&&) of a type unrelated to int. That violates strict aliasing.

So no, you can't just do that.

And no, it doesn't matter that EmptyBase has no subobjects. Copying an object accesses its value representation. Even if the derived class will overwrite anything it does (since the empty base class has no storage of its own).

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • And if the code was modified to (just to get OP's code to compile): https://i.imgur.com/IH2dwnt.png then the parameter passed in isn't used.. – Brandon Jan 16 '18 at 02:50
  • 3
    @Brandon: Performing the cast is not where the problem happens. You get UB when you *use the result* of an illegal cast. If a bad cast falls in the woods and nobody is there to hear it, you don't get UB. – Nicol Bolas Jan 16 '18 at 02:51
  • Does an empty base class that doesn't actually use the reference in its constructor still cause UB? That doesn't access the value representation? – Curious Jan 16 '18 at 02:56
  • Add `EmptyBase(EmptyBase&&){}` ctor and what happens? I ask this because it fits the OP's title if not example. – Yakk - Adam Nevraumont Jan 16 '18 at 03:06
  • My reading of the standard is that it is similar to the behavior of dereferencing a NULL pointer. That is, it's actually not clear if dereferencing it is enough or if you have to do something more in that location. – Nicol Bolas Jan 16 '18 at 03:08
  • I think that has the type `EmptyBase&` and not `EmptyBase&&`, this is a strange situation. What if that type is just union type and we use `std::launder` to access said storage as a reference? – Curious Jan 16 '18 at 04:30
  • 1
    Can you clarify why copying an empty object involves accessing it? – Passer By Jan 16 '18 at 18:55
  • An implicitly-defined copy constructor of a union copies the object representation. An implicitly-defined copy constructor of a non-union class performs a memberwise copy. – T.C. Jan 19 '18 at 17:43
  • See https://stackoverflow.com/questions/48341888/does-copying-an-empty-object-involve-accessing-it – Passer By Jan 25 '18 at 19:44