Last active
July 7, 2024 18:11
-
-
Save DrkWithT/69b86d4a2895ba607848042c9aa55993 to your computer and use it in GitHub Desktop.
A toy C++ optional implementation for educational purposes. Follows some cppreference points.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdexcept> | |
#include <type_traits> | |
#include <utility> | |
#include <iostream> | |
// Optional v2: semi-serious attempt using cppref as a reference. | |
// NOTE: There are some caveats e.g not handling pointer types, etc. | |
namespace toy { | |
// BRIEF: placeholder for no value | |
struct MyNullopt {}; | |
// BRIEF: runtime error inheriting from base exception as per cppref | |
class BadOptAccess : public std::exception | |
{ | |
const char *what() const noexcept override | |
{ | |
return "Bad optional access, no value.\n"; | |
} | |
}; | |
/// BRIEF: Optional's utility to store nothing or desired value | |
template <typename T> | |
struct StorageBase | |
{ | |
// BRIEF: union to optionally contain a value of T if Optional is empty | |
union Mono | |
{ | |
MyNullopt filler; | |
// NOTE: remove const / volatile qualifiers so I can store the actual data type | |
std::remove_cv_t<T> value; | |
} data; | |
// BRIEF: tracks state of Optional: "full" or empty | |
bool fill_flag; | |
constexpr StorageBase() noexcept : fill_flag{false} {} | |
constexpr StorageBase(MyNullopt blah) noexcept : fill_flag{false} {} | |
constexpr StorageBase(T &arg) noexcept : fill_flag{true} | |
{ | |
data.value = arg; | |
} | |
constexpr StorageBase(T &&arg) noexcept : fill_flag{true} | |
{ | |
data.value = arg; | |
} | |
// NOTE: I will use utility assign methods manually, so assignment overloads are N/A... | |
constexpr bool getFlag() const { return fill_flag; } | |
void clearFlag() | |
{ | |
fill_flag = false; | |
} | |
const T& getValue() const | |
{ | |
if (getFlag()) | |
return data.value; | |
throw BadOptAccess{}; | |
} | |
void assignValue(MyNullopt nul_arg) | |
{ | |
data.filler = nul_arg; | |
clearFlag(); | |
} | |
template <typename FwdTp> | |
void assignValue(FwdTp&& fw_arg) | |
{ | |
using naked_tp = std::remove_reference_t<FwdTp>; | |
// NOTE: Mono data's active member is now that of stored type, fill_flag logic prevents UB access of other inactives | |
data.value = std::forward<naked_tp>(fw_arg); | |
fill_flag = true; | |
} | |
void resetValue() noexcept | |
{ | |
if (getFlag()) | |
data.value.~T(); | |
clearFlag(); | |
} | |
~StorageBase() | |
{ | |
resetValue(); | |
} | |
}; | |
template <typename T> | |
class Optional : StorageBase<T> | |
{ | |
private: | |
using tp_store_t = StorageBase<T>; | |
public: | |
constexpr Optional() noexcept : tp_store_t() {} | |
constexpr Optional(MyNullopt blah) noexcept : tp_store_t(blah) {} | |
constexpr Optional(const Optional<T>& other) | |
{ | |
if (other.hasItem()) | |
{ | |
tp_store_t::assignValue(other.getValue()); | |
return; | |
} | |
tp_store_t::assignValue(MyNullopt {}); | |
} | |
constexpr Optional(Optional&& other) noexcept | |
{ | |
if constexpr (other.hasItem()) | |
{ | |
tp_store_t::assignValue(std::move(other.getItem())); | |
return; | |
} | |
tp_store_t::assignValue(MyNullopt {}); | |
} | |
template <typename Tp = T> | |
constexpr Optional(Tp&& arg) : tp_store_t(arg) {} | |
/// Assignment overloads | |
Optional& operator=(MyNullopt&& blah) noexcept | |
{ | |
tp_store_t::assignValue(blah); | |
return *this; | |
} | |
Optional& operator=(const Optional<T>& other) | |
{ | |
if (&other == this || (!hasItem() && !other.hasItem())) | |
return *this; | |
if (!other.hasItem()) | |
resetSelf(); | |
else | |
tp_store_t::assignValue(other.getItem()); | |
return *this; | |
} | |
Optional& operator=(Optional<T>&& other) | |
{ | |
if (!hasItem() || !other.hasItem()) | |
return *this; | |
// NOTE: moved-from optional still contains a value unlike the commonly destructive moves- just copy to respect that. Not the best solution tbh | |
if (!other.hasItem()) | |
resetSelf(); | |
else | |
tp_store_t::assignValue(other.getItem()); | |
return *this; | |
} | |
template <typename FwdType = T> | |
constexpr Optional& operator=(FwdType&& fw_arg) | |
{ | |
tp_store_t::assignValue(fw_arg); | |
return *this; | |
} | |
/// Observers | |
constexpr bool hasItem() const | |
{ | |
return tp_store_t::getFlag(); | |
} | |
const T& getItem() const | |
{ | |
return tp_store_t::getValue(); | |
} | |
explicit operator bool() const noexcept { return hasItem(); } | |
/// Modifiers | |
void resetSelf() noexcept | |
{ | |
tp_store_t::resetValue(); | |
} | |
~Optional() | |
{ | |
resetSelf(); | |
} | |
}; | |
} // namespace toy | |
template <typename OptTp> | |
using MyOpt = toy::Optional<OptTp>; | |
int main() | |
{ | |
using MyOptNul = toy::MyNullopt; | |
using MyOptErr = toy::BadOptAccess; | |
MyOpt<int> maybe_num_1 {MyOptNul {}}; | |
MyOpt<int> maybe_num_2 = 42; | |
MyOpt<int> maybe_num_3 {maybe_num_1}; | |
// print test values... | |
std::boolalpha(std::cout); | |
std::cout << static_cast<bool>(maybe_num_1) << '\n'; // false | |
std::cout << maybe_num_1.hasItem() << '\n'; // false | |
try | |
{ | |
std::cout << maybe_num_1.getItem() << '\n'; | |
} | |
catch (const MyOptErr& e) | |
{ | |
std::cout << "Nothing is in maybe_num_1!\n"; | |
} | |
std::cout << static_cast<bool>(maybe_num_2) << '\n'; // true | |
std::cout << maybe_num_2.hasItem() << '\n'; // true | |
std::cout << maybe_num_2.getItem() << '\n'; // 42 | |
maybe_num_2.resetSelf(); // should destroy its value and become empty | |
std::cout << maybe_num_2.hasItem() << '\n'; // false | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment