cant erase u

10s 256 MB
Exclusive Harder 6
Design PatternsMemory Management

Implement InplaceDrawable<Cap>, a type-erased, inline-storage handle that can hold any shape-like object and dispatch to its behavior at runtime — without inheritance, and without ever allocating on the heap.

This is a type erasure problem: you will hand-build the runtime dispatch mechanism (a vtable of function pointers) that a virtual base class would give you for free — except here the erased types share no base class, and the storage lives in a fixed inline buffer.

This problem is designed to require careful handling of:

  • a hand-rolled multi-method vtable (one entry per erased operation)
  • placement new into a fixed inline buffer (small-buffer optimization, no heap)
  • correct copy / move / destroy of an object whose static type has been erased

InplaceDrawable<Cap> erases any type T that models the Drawable concept:

C++
void   draw() const;   // renders (return value ignored)
double area() const;   // returns the area

and is copy-constructible and nothrow-move-constructible.

Once a T is erased, the handle exposes only draw(), area(), and value semantics (copy / move / destroy). Any other member of T is sealed off — the handle is a closed interface, exactly like an abstract base exposing only those methods.

The erased object is stored inline, in a buffer of Cap bytes inside the handle. You may assume every erased type fits within Cap and satisfies alignof(T) <= Align, meaning you do not need to handle or reject oversized types.

C++
#include <cstddef>
#include <new>
#include <utility>
#include <type_traits>

template <std::size_t Cap, std::size_t Align = alignof(std::max_align_t)>
class InplaceDrawable {
public:
    InplaceDrawable() noexcept;                 // empty handle

    // Erase any Drawable T. 
    template <class T>
    InplaceDrawable(T&& obj);

    InplaceDrawable(const InplaceDrawable&);            // deep copy of erased object
    InplaceDrawable(InplaceDrawable&&) noexcept;        // move of erased object
    InplaceDrawable& operator=(const InplaceDrawable&);
    InplaceDrawable& operator=(InplaceDrawable&&) noexcept;
    ~InplaceDrawable();

    void   draw() const;                        // dispatch; throws if empty
    double area() const;                        // dispatch; throws if empty

    explicit operator bool() const noexcept;    // true iff holding an object
    void reset() noexcept;                       // destroy held object, become empty

private:
    // You decide the internal representation. Suggested: an inline aligned
    // byte buffer + a pointer to a per-erased-type vtable.
};

Because the erased types share no base class, you must synthesize the dynamic dispatch yourself. At the point of construction — where the concrete type T is still known — capture function pointers that know how to operate on a T:

C++
struct VTable {
    void   (*draw)(const void* self);
    double (*area)(const void* self);
    void   (*copy)(void* dst, const void* src);    // placement-new copy into dst
    void   (*move)(void* dst, void* src) noexcept;  // placement-new move into dst
    void   (*destroy)(void* self) noexcept;
};

Each entry static_casts the void* back to T* and calls the real operation. Generate one VTable per erased T (a function-local static works well) and store a pointer to it in the handle. An empty handle has a null vtable pointer.

This is a multi-method vtable: unlike std::function (which erases a single operator()), you erase five operations. copy / move / destroy are what make value semantics work on an object whose type you have forgotten — only code generated while T was known can correctly copy/move/destroy a T.

  • The erased object is constructed in place in the handle's inline buffer using placement new. The handle must perform zero heap allocations for construction, copy, move, and destruction of a fitting type. (The grader interposes operator new and asserts the count never moves for a type whose own members do not allocate.)
  • Copy and move must relocate the erased object through the captured copy/move constructor, never by copying the raw buffer bytes. A memcpy of the inline buffer is incorrect for any non-trivially-relocatable type (e.g. one holding a pointer into its own storage, or a std::string): after a byte copy the object's internal invariants are broken. The grader tests a self-referential erased type and will detect a raw-byte relocation.
  • Move-constructing / move-assigning a handle must move the erased object into the destination's buffer (via the move vtable entry) and then destroy the source's erased object, leaving the source empty (bool(x) == false).
  • After a move, reading the moved-from handle's value is not required to work, but the handle must be safely destructible and re-assignable.

draw() / area()

  • Dispatch through the vtable to the erased object.
  • If the handle is empty, throw (any exception type is acceptable; the grader only checks that calling an empty handle throws).

Copy

  • Produces an independent erased object: mutating or destroying the source afterward must not affect the copy, and vice versa. (The grader copies a handle holding a stateful erased object, reassigns the source, and checks the copy is unchanged.)

Destruction

  • The handle's destructor destroys the held erased object exactly once. The grader uses a lifetime-tracked erased type and checks ctor == dtor with no double-destruction.

T must be:

  • Drawable: has draw() const and area() const (returning something convertible to double),
  • copy-constructible (this handle is copyable),
  • nothrow-move-constructible

The grader only erases types that fit; you may assume sizeof(T) <= Cap and alignof(T) <= Align.

  • draw, area, operator bool, destroy: O(1) plus the cost of the erased operation.
  • Copy / move: O(1) plus the cost of the erased type's copy / move constructor.
  • All operations: zero heap allocation by the handle itself.

Single-threaded. No threads, timing, randomness, or OS-specific behavior.

  • The erased types (shapes), the buffer sizes used by the grader, and the lifetime-tracking machinery are provided by the grader. You implement only InplaceDrawable.
  • You may use <cstddef>, <new>, <utility>, <type_traits>. You may not use std::function, std::any, std::variant, or std::move_only_function as the erasure mechanism — the point is to build the vtable yourself. (These identifiers are reserved and will fail to compile if used.)
  • The grader compiles with -fsanitize=address,undefined. A raw-buffer copy or move (memcpy) of a non-trivially-relocatable erased type, a destroy through the wrong type, or a leaked erased object are all detected.

Pointer provenance

The erased object is constructed via placement new into reused inline storage across the handle's lifetime (e.g. reset() then reassign). Produce and use the object pointer via std::launder(reinterpret_cast<T*>(buf)) where the standard requires it for correctness under strict aliasing.

C++
struct Circle { double r; void draw() const {} double area() const { return 3.14159*r*r; } };
struct Square { double s; void draw() const {} double area() const { return s*s; } };

InplaceDrawable<32> d = Circle{2.0};
d.area();                       // ~12.566

d = Square{3.0};                // erase a different, unrelated type
d.area();                       // 9.0

InplaceDrawable<32> e = d;      // deep copy of the erased Square
InplaceDrawable<32> f = std::move(d);   // move; d is now empty
bool(d);                        // false
Accepted 2/2
Acceptance 100%
Loading editor...
Input
Running against custom cases...
Expected Output