Home > Net >  new (ptr) T() == static_cast<T*>(ptr)?
new (ptr) T() == static_cast<T*>(ptr)?

Time:10-28

I want to implement something like rust's dyn trait(I know this doesn't work for multiple inheritance)

template<template<typename> typename Trait>
class Dyn
{
    struct _SizeCaler:Trait<void>{ void* _p;};
    char _buffer[sizeof(_SizeCaler)];
public:
    template<typename T>
    Dyn(T* value){
        static_assert(std::is_base_of_v<Trait<void>,Trait<T>>
            ,"error Trait T,is not derive from trait<void>"
        );

        static_assert(sizeof(_buffer) >= sizeof(Trait<T>)
            ,"different vtable imple cause insufficient cache"
        );

        new (_buffer)Trait<T>{value}; 
    }
    Trait<void>* operator->(){
        return static_cast<Trait<void>*>(reinterpret_cast<void*>(_buffer));
    }
};

template<template<typename> typename Trait,typename T>
struct DynBase:Trait<void>
{
protected:
    T* self;
public:
    DynBase(T* value):self(value){}
};

struct Point{
    double x,y;
};

struct Rect{
    Point m_leftDown,m_rightUp;
    Rect(Point p1,Point p2){
        m_leftDown = Point{std::min(p1.x,p2.x),std::min(p1.y,p2.y)}; 
        m_rightUp = Point{std::max(p1.x,p2.x),std::max(p1.y,p2.y)}; 
    }
};

template<typename = void>
struct Shape;

template<>
struct Shape<void>
{
    virtual double area() = 0;
};

template<>
struct Shape<Rect> final
    :DynBase<Shape,Rect>
{
    using DynBase<Shape,Rect>::DynBase;
    double area() override{
        return (self->m_rightUp.x - self->m_leftDown.x )
            * (self->m_rightUp.y - self->m_leftDown.y);
    }
};

void printShape(Dyn<Shape> ptr)
{
    std::cout << ptr->area();
}

int main()
{
    Rect r{{1,2},{3,4}};
    printShape(&r);
}

but I found that the c standard may not be able to derive “new (ptr) T() == static_cast<T*>(ptr)“? So conversely, “static_cast<T*>(ptr) == new (ptr) T()” cannot prove

Trait<void>* operator->(){ return static_cast<Trait<void>*>(reinterpret_cast<void*>(_buffer)); }

Other attempts

a abstruct class can't be a union member(why?),and I can't use placement new to calculate offset at compile time, because Trait is an abstract class.

So does the standard specify the validity of static_cast<T*>(ptr) == new (ptr) T()?

CodePudding user response:

The expressions new (ptr) T() and static_cast<T*>(ptr), where ptr has type void*, will return the same address, ptr (as long as T is a scalar type -- array types are allowed to have overhead when dynamically allocated)

However, the semantics are quite different.

  • In new (ptr) T(), a new object of type T is created at the specified address. Any prior value is lost.

  • In static_cast<T*>(ptr), there must already be an object of type T at the specified address, or else you are setting yourself up to violate the strict aliasing rule.


If ptr had a type other than void*, it would need to be related to T by inheritance or trivial adjustments (such as changing signedness or adding const or volatile), and the address might have an offset applied as a result of multiple inheritance. But that will never happen in the code of this question.

CodePudding user response:

Whether or not new (ptr) T() == static_cast<T*>(ptr) is true is not your actual problem.

The actual problem is that the class Trait<T> you are constructing is not standard-layout and as a consequence the Trait<void> subobject of the Trait<T> object is neither pointer-interconvertible with the Trait<T> object, nor does it have to be located at the same address. As a consequence you can't cast from _buffer to Trait<void>* without knowing T or having a pointer to Trait<void> specifically.

In other words, you need to store an additional pointer Trait<void>* in the class which you initialize from the result of new (_buffer)Trait<T>{value}; (via implicit conversion), or alternatively at least an offset between Trait<T> and its Trait<void> subobject. (In the latter case beware that you need to add a std::launder after the reinterpret_cast.)

Furthmore, as mentioned in the comments, you need to assure that the buffer is suitably aligned for an object of type Trait<T>. That requires adding an alignas on the buffer so that it is suitable for all T you want to consider and then there should probably be a static_assert on the alignment analogous to the one for size as well.


Also, static_cast<Trait<void>*>(reinterpret_cast<void*>(_buffer)) is equivalent to reinterpret_cast<Trait<void>*>(_buffer). No need for the double cast.

CodePudding user response:

Other question:

an abstract class can't be a union member(why?)

because it makes no sense to have an abstract class as a union member. In order to use a union member, you need to instantiate an object of the union member's type to store in the union. But you can't instantiate an object of an abstract class, so you could never use the abstract class union member for anything.

  • Related