Replace Either with std::variant (#1731)

This commit is contained in:
Rangi
2025-07-08 13:59:03 -04:00
committed by GitHub
parent 35962dedc4
commit fda54fd0c3
17 changed files with 94 additions and 271 deletions

View File

@@ -10,16 +10,16 @@
#include <stdint.h>
#include <stdio.h>
#include <string>
#include <variant>
#include <vector>
#include "either.hpp"
#include "linkdefs.hpp"
#include "asm/lexer.hpp"
struct FileStackNode {
FileStackNodeType type;
Either<
std::variant<
std::vector<uint32_t>, // NODE_REPT
std::string // NODE_FILE, NODE_MACRO
>
@@ -34,13 +34,13 @@ struct FileStackNode {
uint32_t ID = UINT32_MAX;
// REPT iteration counts since last named node, in reverse depth order
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }
std::vector<uint32_t> const &iters() const { return data.get<std::vector<uint32_t>>(); }
std::vector<uint32_t> &iters() { return std::get<std::vector<uint32_t>>(data); }
std::vector<uint32_t> const &iters() const { return std::get<std::vector<uint32_t>>(data); }
// File name for files, file::macro name for macros
std::string &name() { return data.get<std::string>(); }
std::string const &name() const { return data.get<std::string>(); }
std::string &name() { return std::get<std::string>(data); }
std::string const &name() const { return std::get<std::string>(data); }
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
FileStackNode(FileStackNodeType type_, std::variant<std::vector<uint32_t>, std::string> data_)
: type(type_), data(data_) {}
std::string const &dump(uint32_t curLineNo) const;

View File

@@ -8,9 +8,9 @@
#include <optional>
#include <stdint.h>
#include <string>
#include <variant>
#include <vector>
#include "either.hpp"
#include "platform.hpp" // SSIZE_MAX
// This value is a compromise between `LexerState` allocation performance when `mmap` works, and
@@ -97,7 +97,7 @@ struct LexerState {
bool expandStrings;
std::deque<Expansion> expansions; // Front is the innermost current expansion
Either<ViewedContent, BufferedContent> content;
std::variant<std::monostate, ViewedContent, BufferedContent> content;
~LexerState();

View File

@@ -5,15 +5,15 @@
#include <stdint.h>
#include <string>
#include <variant>
#include <vector>
#include "either.hpp"
#include "linkdefs.hpp"
struct Symbol;
struct Expression {
Either<
std::variant<
int32_t, // If the expression's value is known, it's here
std::string // Why the expression is not known, if it isn't
>
@@ -22,8 +22,8 @@ struct Expression {
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
bool isKnown() const { return data.holds<int32_t>(); }
int32_t value() const { return data.get<int32_t>(); }
bool isKnown() const { return std::holds_alternative<int32_t>(data); }
int32_t value() const { return std::get<int32_t>(data); }
int32_t getConstVal() const;
Symbol const *symbolOf() const;

View File

@@ -1,176 +0,0 @@
// 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

View File

@@ -10,15 +10,15 @@
#include <streambuf>
#include <string.h>
#include <string>
#include <variant>
#include "either.hpp"
#include "helpers.hpp" // assume
#include "platform.hpp"
#include "gfx/main.hpp"
#include "gfx/warning.hpp"
class File {
Either<std::streambuf *, std::filebuf> _file;
std::variant<std::streambuf *, std::filebuf> _file;
public:
File() : _file(nullptr) {}
@@ -27,8 +27,7 @@ public:
// Returns `nullptr` on error, and a non-null pointer otherwise.
File *open(std::string const &path, std::ios_base::openmode mode) {
if (path != "-") {
_file.emplace<std::filebuf>();
return _file.get<std::filebuf>().open(path, mode) ? this : nullptr;
return _file.emplace<std::filebuf>().open(path, mode) ? this : nullptr;
} else if (mode & std::ios_base::in) {
assume(!(mode & std::ios_base::out));
_file.emplace<std::streambuf *>(std::cin.rdbuf());
@@ -46,8 +45,8 @@ public:
return this;
}
std::streambuf &operator*() {
return _file.holds<std::filebuf>() ? _file.get<std::filebuf>()
: *_file.get<std::streambuf *>();
return std::holds_alternative<std::filebuf>(_file) ? std::get<std::filebuf>(_file)
: *std::get<std::streambuf *>(_file);
}
std::streambuf const &operator*() const {
// The non-`const` version does not perform any modifications, so it's okay.
@@ -60,9 +59,9 @@ public:
}
char const *c_str(std::string const &path) const {
return _file.holds<std::filebuf>() ? path.c_str()
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
: "<stdout>";
return std::holds_alternative<std::filebuf>(_file) ? path.c_str()
: std::get<std::streambuf *>(_file) == std::cin.rdbuf() ? "<stdin>"
: "<stdout>";
}
};

View File

@@ -6,9 +6,9 @@
#include <stdint.h>
#include <stdio.h>
#include <string>
#include <variant>
#include <vector>
#include "either.hpp"
#include "linkdefs.hpp"
// Variables related to CLI options
@@ -39,7 +39,8 @@ extern bool disablePadding;
struct FileStackNode {
FileStackNodeType type;
Either<
std::variant<
std::monostate, // Default constructed; `.type` and `.data` must be set manually
std::vector<uint32_t>, // NODE_REPT
std::string // NODE_FILE, NODE_MACRO
>
@@ -50,11 +51,11 @@ struct FileStackNode {
uint32_t lineNo;
// REPT iteration counts since last named node, in reverse depth order
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }
std::vector<uint32_t> const &iters() const { return data.get<std::vector<uint32_t>>(); }
std::vector<uint32_t> &iters() { return std::get<std::vector<uint32_t>>(data); }
std::vector<uint32_t> const &iters() const { return std::get<std::vector<uint32_t>>(data); }
// File name for files, file::macro name for macros
std::string &name() { return data.get<std::string>(); }
std::string const &name() const { return data.get<std::string>(); }
std::string &name() { return std::get<std::string>(data); }
std::string const &name() const { return std::get<std::string>(data); }
std::string const &dump(uint32_t curLineNo) const;
};

View File

@@ -7,8 +7,8 @@
#include <stdint.h>
#include <string>
#include <variant>
#include "either.hpp"
#include "linkdefs.hpp"
struct FileStackNode;
@@ -27,14 +27,14 @@ struct Symbol {
ExportLevel type;
FileStackNode const *src;
int32_t lineNo;
Either<
std::variant<
int32_t, // Constants just have a numeric value
Label // Label values refer to an offset within a specific section
>
data;
Label &label() { return data.get<Label>(); }
Label const &label() const { return data.get<Label>(); }
Label &label() { return std::get<Label>(data); }
Label const &label() const { return std::get<Label>(data); }
};
void sym_ForEach(void (*callback)(Symbol &));