mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Use a custom generic tagged union Either instead of std::variant for efficiency (#1476)
* Implement custom generic tagged union `Either` This should be more efficient than `std::variant`, while still keeping runtime safety as it `assert`s when `get`ting values. * Use `Either` for RPN expressions * Use `Either` for file stack node data * Use `Either` for `File` buffer * Use `Either` for `STRFMT` args * Use `Either` for RGBLINK symbol values * Support an equivalent of `std::monostate` for `Either` * Use `Either` for lexer tokens * Use `Either` for symbol values * Use `Either` for lexer mmap/buffer state
This commit is contained in:
176
include/either.hpp
Normal file
176
include/either.hpp
Normal file
@@ -0,0 +1,176 @@
|
||||
/* 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((T *)nullptr);
|
||||
}
|
||||
template<typename T>
|
||||
auto const &field() const {
|
||||
return pick((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;
|
||||
}
|
||||
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
|
||||
Reference in New Issue
Block a user