template <typename T>
class Container {
private:
std::aligned_storage_t<sizeof(T), alignof(T)> data;
bool is_active = false;
T& as_type() { return reinterpret_cast<T&>(data); }
public:
Container() = default;
void construct() {
if (is_active) throw ...;
new (&data) T();
is_active = true;
}
};
Using as_type
before the type is constructed (or after it is destructed) will be UB. I probably wouldn't actually have a member construct
though. I would just actually focus on writing the different members that you need. In this case you should probably just have a constructor that takes a T
:
Container::Container(const T& t)
: is_active(true)
{
new (&datamember) T(t);
}
One thing to note is that as you keep implementing, you'll start to see some of the downsides of this approach (in its simplest form). You have to have a boolean of course, to check whether things are already constructed, and so on. So you'll have destructor, copy, move operators, that check a boolean, and then do something.
However, none of this is needed if the contained type is itself trivial, like an integer or something. This can be a really large performance hit in some situations. So the standard mandates that these sort of traits carry through from the contained type to the optional. That is, if the contained type is trivially destructible, then so will be an optional on that type. Therefore:
std::cerr << std::is_trivially_destructible_v<std::optional<int>>;
Will print out 1
. Implementing this however requires more complexity, tricks like conditional inheritance for example (just one possible approach).