mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
177 lines
4.6 KiB
C++
177 lines
4.6 KiB
C++
// SPDX-License-Identifier: MIT
|
|
|
|
#ifndef RGBDS_EITHER_HPP
|
|
#define RGBDS_EITHER_HPP
|
|
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "helpers.hpp" // assume
|
|
|
|
template<typename T1, typename T2>
|
|
union Either {
|
|
typedef T1 type1;
|
|
typedef T2 type2;
|
|
|
|
private:
|
|
template<typename T, unsigned V>
|
|
struct Field {
|
|
constexpr static unsigned tag_value = V;
|
|
|
|
unsigned tag = tag_value;
|
|
T value;
|
|
|
|
Field() : value() {}
|
|
Field(T &value_) : value(value_) {}
|
|
Field(T const &value_) : value(value_) {}
|
|
Field(T &&value_) : value(std::move(value_)) {}
|
|
};
|
|
|
|
// The `_tag` unifies with the first `tag` member of each `struct`.
|
|
constexpr static unsigned nulltag = 0;
|
|
unsigned _tag = nulltag;
|
|
Field<T1, 1> _t1;
|
|
Field<T2, 2> _t2;
|
|
|
|
// Value accessors; the function parameters are dummies for overload resolution.
|
|
// Only used to implement `field()` below.
|
|
auto &pick(T1 *) { return _t1; }
|
|
auto const &pick(T1 *) const { return _t1; }
|
|
auto &pick(T2 *) { return _t2; }
|
|
auto const &pick(T2 *) const { return _t2; }
|
|
|
|
// Generic field accessors; for internal use only.
|
|
template<typename T>
|
|
auto &field() {
|
|
return pick(static_cast<T *>(nullptr));
|
|
}
|
|
template<typename T>
|
|
auto const &field() const {
|
|
return pick(static_cast<T *>(nullptr));
|
|
}
|
|
|
|
public:
|
|
// Equivalent of `std::monostate` for `std::variant`s.
|
|
Either() : _tag() {}
|
|
// These constructors cannot be generic over the value type, because that would prevent
|
|
// constructible values from being inferred, e.g. a `const char *` string literal for an
|
|
// `std::string` field value.
|
|
Either(T1 &value) : _t1(value) {}
|
|
Either(T2 &value) : _t2(value) {}
|
|
Either(T1 const &value) : _t1(value) {}
|
|
Either(T2 const &value) : _t2(value) {}
|
|
Either(T1 &&value) : _t1(std::move(value)) {}
|
|
Either(T2 &&value) : _t2(std::move(value)) {}
|
|
|
|
// Destructor manually calls the appropriate value destructor.
|
|
~Either() {
|
|
if (_tag == _t1.tag_value) {
|
|
_t1.value.~T1();
|
|
} else if (_tag == _t2.tag_value) {
|
|
_t2.value.~T2();
|
|
}
|
|
}
|
|
|
|
// Copy assignment operators for each possible value.
|
|
Either &operator=(T1 const &value) {
|
|
_t1.tag = _t1.tag_value;
|
|
new (&_t1.value) T1(value);
|
|
return *this;
|
|
}
|
|
Either &operator=(T2 const &value) {
|
|
_t2.tag = _t2.tag_value;
|
|
new (&_t2.value) T2(value);
|
|
return *this;
|
|
}
|
|
|
|
// Move assignment operators for each possible value.
|
|
Either &operator=(T1 &&value) {
|
|
_t1.tag = _t1.tag_value;
|
|
new (&_t1.value) T1(std::move(value));
|
|
return *this;
|
|
}
|
|
Either &operator=(T2 &&value) {
|
|
_t2.tag = _t2.tag_value;
|
|
new (&_t2.value) T2(std::move(value));
|
|
return *this;
|
|
}
|
|
|
|
// Copy assignment operator from another `Either`.
|
|
Either &operator=(Either other) {
|
|
if (other._tag == other._t1.tag_value) {
|
|
*this = other._t1.value;
|
|
} else if (other._tag == other._t2.tag_value) {
|
|
*this = other._t2.value;
|
|
} else {
|
|
_tag = nulltag; // LCOV_EXCL_LINE
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
// Copy constructor from another `Either`; implemented in terms of value assignment operators.
|
|
Either(Either const &other) {
|
|
if (other._tag == other._t1.tag_value) {
|
|
*this = other._t1.value;
|
|
} else if (other._tag == other._t2.tag_value) {
|
|
*this = other._t2.value;
|
|
} else {
|
|
_tag = nulltag;
|
|
}
|
|
}
|
|
|
|
// Move constructor from another `Either`; implemented in terms of value assignment operators.
|
|
Either(Either &&other) {
|
|
if (other._tag == other._t1.tag_value) {
|
|
*this = std::move(other._t1.value);
|
|
} else if (other._tag == other._t2.tag_value) {
|
|
*this = std::move(other._t2.value);
|
|
} else {
|
|
_tag = nulltag;
|
|
}
|
|
}
|
|
|
|
// Equivalent of `.emplace<T>()` for `std::variant`s.
|
|
template<typename T, typename... Args>
|
|
void emplace(Args &&...args) {
|
|
this->~Either();
|
|
if constexpr (std::is_same_v<T, T1>) {
|
|
_t1.tag = _t1.tag_value;
|
|
new (&_t1.value) T1(std::forward<Args>(args)...);
|
|
} else if constexpr (std::is_same_v<T, T2>) {
|
|
_t2.tag = _t2.tag_value;
|
|
new (&_t2.value) T2(std::forward<Args>(args)...);
|
|
} else {
|
|
_tag = nulltag;
|
|
}
|
|
}
|
|
|
|
// Equivalent of `std::holds_alternative<std::monostate>()` for `std::variant`s.
|
|
bool empty() const { return _tag == nulltag; }
|
|
|
|
// Equivalent of `std::holds_alternative<T>()` for `std::variant`s.
|
|
template<typename T>
|
|
bool holds() const {
|
|
if constexpr (std::is_same_v<T, T1>) {
|
|
return _tag == _t1.tag_value;
|
|
} else if constexpr (std::is_same_v<T, T2>) {
|
|
return _tag == _t2.tag_value;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Equivalent of `std::get<T>()` for `std::variant`s.
|
|
template<typename T>
|
|
auto &get() {
|
|
assume(holds<T>());
|
|
return field<T>().value;
|
|
}
|
|
template<typename T>
|
|
auto const &get() const {
|
|
assume(holds<T>());
|
|
return field<T>().value;
|
|
}
|
|
};
|
|
|
|
#endif // RGBDS_EITHER_HPP
|