-4
#include <iostream>

template<typename T>
struct printer {

    virtual const T* get(size_t& sz) const = 0;

    void print() {
        size_t sz;
        const T* _t = get(sz); //tries to access 'this'
        for (size_t t = 0; t < sz; t++)
            std::cout << _t[t] << std::endl;
    }
};

template<typename T, size_t sz>
struct mask_t : public printer<T> {
    T data[sz];

    virtual const T* get(size_t& _sz) const override {
        _sz = sz;
        return data;
    }
};

int main(int argc, char** argv) {

    char* buffer = new char[1024];
    {
        using mask_f = mask_t<float, 12>;
        using mask_i = mask_t<int, 12>;

        mask_f* mf = reinterpret_cast<mask_f*>(buffer + 42);
        mask_i* mi = reinterpret_cast<mask_i*>(buffer + 42);

        //'this' is uninitialized
        mf->print();
        mi->print();

        mask_f _mf = *mf;
        mask_i _mi = *mi;

        //ok
        _mf.print();
        _mi.print();
    }
    delete[] buffer;

    return 0;
}

print() tries to access this when invoking get() is it because of a vfptr lookup ? In other words is this impossible to do ?

Edit : I know I can create a new mask_t with either new or as I have done here by dereferencing the pointer. Then mask_t::this is defined.

The reason I don't want to create the instance is for performance issues [and that's not visible in my example I admit]. If you want to answer please address the only question in this post.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
PinkTurtle
  • 6,942
  • 3
  • 25
  • 44
  • why? `mask_t` can simply wrap the span here. – apple apple Feb 27 '21 at 19:35
  • @appleapple I don't understand what you mean sorry. You mean wrap print() ? This is just an example. – PinkTurtle Feb 27 '21 at 19:36
  • why you need this? In your example `mask_t` can simply accept a pointer and store as its data. – apple apple Feb 27 '21 at 19:38
  • There is no object of type `mask_f` at the address `buffer + 42`. Pretending that there is one is undefined behaviour. – n. m. could be an AI Feb 27 '21 at 19:38
  • 2
    `mask_i` and `mask_f` should be constructed, this can be done with placement new. `buffer + 42` where `buffer` is `new char[1024];` may not be a good alignment. Could use `std::align` `mask_i` and `mask_f` cannot safely alias. – Alex Guteniev Feb 27 '21 at 19:39
  • The behaviour of `mi->print()` and `mf->print()` (in effect, treating a dynamically allocated array of char as if it contains a (templated) `struct` or `class` type at some offset, when it actually doesn't) is undefined. Generally speaking, although `reinterpret_cast` can be used to force the compiler to not diagnose an error it would otherwise diagnose (as you have done), that doesn't make subsequent code well-defined. – Peter Feb 27 '21 at 19:41
  • @RichardCritten yes this is exactly what I mean to do - no one answered my question tho. – PinkTurtle Feb 27 '21 at 19:41
  • *In other words is this impossible to do?* No, not impossible: you can use placement `new`. Yes, impossible: you can't just grab an arbitrary chunk of memory and pretend there is an object there. – Eljay Feb 27 '21 at 20:14
  • @PinkTurtle: "*The reason I don't want to create the instance if for performance issues*" If your code can't suffer the performance of calling placement-`new` (which only incurs the cost of calling a constructor), then you shouldn't be creating these objects *at all*. – Nicol Bolas Mar 02 '21 at 16:06
  • @PinkTurtle: "*I am reading memory of a remote process.*" The C++ standard doesn't let you do that. Or rather, it doesn't allow you to access some *other processes* objects as if they were created by this one. – Nicol Bolas Mar 02 '21 at 16:24

2 Answers2

2

This is not valid code regardless of the types. In C++, you can't just cast a random pointer to an object and pretend one exists.

And yes, in C++20, they do allow you to do that under certain circumstances. But even there, those circumstances do not include operations on types with virtual member functions (as they are insufficiently trivial).

Just use placement-new to construct the object. That's how you're supposed to create objects in storage.


print() tries to access this when invoking get() is it because of a vfptr lookup ?

Does it matter? That's a implementation aspect of how the undefined behavior causes a crash.

This: mask_f* mf = reinterpret_cast<mask_f*>(buffer + 42); causes undefined behavior. As does this: mask_f _mf = *mf;. Both of these access an object which does not exist. Therefore, they both exhibit undefined behavior.

That a particular compiler (version) might make one of these appear to do what you want and might make the other crash is a matter of detail and implementation. Both of these pieces of code are equally nonsensical, and neither can be relied upon to do what you want.

I could explain why the assembly the compiler generated allowed you to get away with UB in one case and not in the other. But that ignores the fact that, either way, you're relying on UB.

I am reading memory of a remote process.

That just isn't a reasonable thing to do in C++. Not for types with virtual members, at any rate. The traditional method is to serialize the data of that type into memory, then de-serialize it in the receiving process back into a new object of that type.

Now yes, if you're willing to write platform-specific hackery, there are ways to pass virtual types between processes like this. They require extracting vtable pointers (from valid objects) and writing them into the object data received from the remote process, thus effectively "fixing" the object in-situ.

But these are platform-specific hackery; if you want something portable, you have to work with serialization.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thank you for your answer but still no-one addressed my question. Let me update my post. – PinkTurtle Mar 02 '21 at 15:52
  • @PinkTurtle: I did address your question. You asked if there was a way to do that. I said "no"; you *have to* use placement-`new`. – Nicol Bolas Mar 02 '21 at 16:05
  • I did not ask that at all my question is the very first sentence in my post. There's a reason I striked the 2nd sentence because it was off-point. – PinkTurtle Mar 02 '21 at 16:09
  • @PinkTurtle: You miss the basic point. Without an object, you cannot call methods on an object. `char* buffer = new char[1024];` contains 1024 `char` objects, which together form one `char[1024]` object, but it contains zero `printer` objects. Therefore, the answer is NO. – MSalters Mar 02 '21 at 16:16
  • No to what? This is not my question. My question is "why" doesn't it work ? vfptr lookup ? – PinkTurtle Mar 02 '21 at 16:19
  • @PinkTurtle: Your question is "Is there a way to reinterpret_cast to a virtual derived* and calling overriden from parent?" That's what it says in your title, and that's what your code and the text beneath it is really about. Your code doesn't work because the C++ standard *says* that it doesn't work. It doesn't work because according to the C++ standard, *there is no object there*. The C++ standard declares accessing an object that doesn't exist as undefined behavior. – Nicol Bolas Mar 02 '21 at 16:23
  • The question title is the question title if you just want to answer that then don't even read my post. The question in my post is : print() tries to access this when invoking get() is it because of a vfptr lookup ?. Obviously I already know it doesn't work - and then Im asking "why?". – PinkTurtle Mar 02 '21 at 16:32
  • 1
    @PinkTurtle: We spelled out the exact why: [3.64 Undefined Behavior](https://eel.is/c++draft/defns.undefined). – MSalters Mar 02 '21 at 16:35
  • This doesn't answer my question which is : is it because of a vfptr lookup ? – PinkTurtle Mar 02 '21 at 16:37
  • 1
    @PinkTurtle: No, we say that it doesn't work because it's 3.64 Undefined Behavior, and it's Undefined Behavior because 6.7.3 Lifetime says so. – MSalters Mar 02 '21 at 16:44
  • @PinkTurtle: "*is it because of a vfptr lookup ?*" That is an implementation detail of your particular compiler. – Nicol Bolas Mar 02 '21 at 17:10
0

"I know I can create a new mask_t with either new or as I have done here by dereferencing the pointer. Then mask_t::this is defined."

That is wrong. An object starts to exist when the body of its constructor is entered. new, including placement new, is a way to cause the constructor to run. Other ways are just declaring a local or global variable. But "dereferencing the pointer" as you assume does not magically cause a constructor to run, and an object to be created. And without an object, this cannot point to the current object.

Instead, you get Undefined Behavior. Anything can happen. Your harddisk could be erased.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • Not my question. – PinkTurtle Mar 02 '21 at 16:21
  • 1
    @PinkTurtle: You wrote the question, it's up to you to make it clear. I quoted literally from your question as it was visible when I answered it. And just to be extremely clear, "Undefined Behavior" in C++ means "you can't do that". – MSalters Mar 02 '21 at 16:23
  • I think you are wrong and derefering + assignement does call the constructor (by copy assignement obviously). The proof is that `_mf.print();` and `_mi.print();` **does** work. `this` is defined. – PinkTurtle Mar 02 '21 at 16:24
  • @PinkTurtle: "*I think you are wrong and derefering + assignement does call the constructor.*" You can believe whatever you like, but that doesn't make you right. The C++ standard is what it is and says what it says, and it doesn't say that your code is valid. – Nicol Bolas Mar 02 '21 at 16:25
  • 1
    @PinkTurtle: See my last sentence. Undefined Behavior means anything can happen. That includes working 10 times like you expect it, and deleting your harddisk on the 11th run. You are wrong to assume that seeing it work a few times is in any way relevant. Also, there is no defined behavior in the presence of undefined behavior. Calling the copy ctor is not meaningful term if the "source" isn't an object to start with. And yes, Undefined Behavior means that `this` can be `purple`, or from another dimension. C++ says what well-defined programs do, and this is not one of them. – MSalters Mar 02 '21 at 16:32
  • Copy assignement is not undefined behaviour and it does call the constructor. – PinkTurtle Mar 02 '21 at 16:33
  • 1
    That statement is only true if there is no other Undefined Behavior before **or after** the copy assignment. In C++ it's well-established that Undefined Behavior can even travel back in time. Not that it matters here; the program is irrevocably broken by `mf->print();`. – MSalters Mar 02 '21 at 16:38