cant erase u
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:
void draw() const; // renders (return value ignored)
double area() const; // returns the areaand 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.
#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:
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 newand 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
memcpyof the inline buffer is incorrect for any non-trivially-relocatable type (e.g. one holding a pointer into its own storage, or astd::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
movevtable 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 == dtorwith no double-destruction.
T must be:
- Drawable: has
draw() constandarea() const(returning something convertible todouble), - 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 usestd::function,std::any,std::variant, orstd::move_only_functionas 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.
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); // falseRunning against custom cases...