Use variant as payload for Result<T, E>
Use std::variant<std::monostate, T, std::unique_ptr<E>> as the payload
for Result allows T to be non-default-constructible and also moves some
bookkeeping from Result into std::variant.
Bug: dawn:2344
Change-Id: I3f7d1aa4b7776de0174052c4379e5abb1ef107c3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/169361
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Sunny Sachanandani <sunnyps@chromium.org>
Auto-Submit: Sunny Sachanandani <sunnyps@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/common/Result.h b/src/dawn/common/Result.h
index 7bcccfc..8000a0f 100644
--- a/src/dawn/common/Result.h
+++ b/src/dawn/common/Result.h
@@ -33,6 +33,7 @@
#include <memory>
#include <type_traits>
#include <utility>
+#include <variant>
#include "dawn/common/Assert.h"
#include "dawn/common/Compiler.h"
@@ -224,7 +225,7 @@
template <typename T, typename E>
class [[nodiscard]] Result {
public:
- Result(T&& success);
+ Result(T success);
Result(std::unique_ptr<E> error);
Result(Result<T, E>&& other);
@@ -235,19 +236,11 @@
bool IsError() const;
bool IsSuccess() const;
- T&& AcquireSuccess();
+ T AcquireSuccess();
std::unique_ptr<E> AcquireError();
private:
- enum PayloadType {
- Success = 0,
- Error = 1,
- Acquired = 2,
- };
- PayloadType mType;
-
- std::unique_ptr<E> mError;
- T mSuccess;
+ std::variant<std::monostate, T, std::unique_ptr<E>> mPayload;
};
// Implementation of Result<void, E>
@@ -478,52 +471,52 @@
// Implementation of Result<T, E>
template <typename T, typename E>
-Result<T, E>::Result(T&& success) : mType(Success), mSuccess(std::move(success)) {}
+Result<T, E>::Result(T success) : mPayload(std::move(success)) {}
template <typename T, typename E>
-Result<T, E>::Result(std::unique_ptr<E> error) : mType(Error), mError(std::move(error)) {}
+Result<T, E>::Result(std::unique_ptr<E> error) : mPayload(std::move(error)) {}
template <typename T, typename E>
Result<T, E>::~Result() {
- DAWN_ASSERT(mType == Acquired);
+ DAWN_ASSERT(std::holds_alternative<std::monostate>(mPayload));
}
template <typename T, typename E>
-Result<T, E>::Result(Result<T, E>&& other)
- : mType(other.mType), mError(std::move(other.mError)), mSuccess(std::move(other.mSuccess)) {
- other.mType = Acquired;
+Result<T, E>::Result(Result<T, E>&& other) {
+ *this = std::move(other);
}
+
template <typename T, typename E>
Result<T, E>& Result<T, E>::operator=(Result<T, E>&& other) {
- mType = other.mType;
- mError = std::move(other.mError);
- mSuccess = std::move(other.mSuccess);
- other.mType = Acquired;
+ DAWN_ASSERT(std::holds_alternative<std::monostate>(mPayload));
+ std::swap(mPayload, other.mPayload);
return *this;
}
template <typename T, typename E>
bool Result<T, E>::IsError() const {
- return mType == Error;
+ return std::holds_alternative<std::unique_ptr<E>>(mPayload);
}
template <typename T, typename E>
bool Result<T, E>::IsSuccess() const {
- return mType == Success;
+ return std::holds_alternative<T>(mPayload);
}
template <typename T, typename E>
-T&& Result<T, E>::AcquireSuccess() {
- DAWN_ASSERT(mType == Success);
- mType = Acquired;
- return std::move(mSuccess);
+T Result<T, E>::AcquireSuccess() {
+ DAWN_ASSERT(IsSuccess());
+ auto payload = std::move(mPayload);
+ mPayload = {};
+ return std::move(std::get<T>(payload));
}
template <typename T, typename E>
std::unique_ptr<E> Result<T, E>::AcquireError() {
- DAWN_ASSERT(mType == Error);
- mType = Acquired;
- return std::move(mError);
+ DAWN_ASSERT(IsError());
+ auto payload = std::move(mPayload);
+ mPayload = {};
+ return std::move(std::get<std::unique_ptr<E>>(payload));
}
} // namespace dawn
diff --git a/src/dawn/tests/unittests/ResultTests.cpp b/src/dawn/tests/unittests/ResultTests.cpp
index 8b5064d..44274e1 100644
--- a/src/dawn/tests/unittests/ResultTests.cpp
+++ b/src/dawn/tests/unittests/ResultTests.cpp
@@ -398,5 +398,58 @@
TestSuccess(&result, {1.0f});
}
+class NonDefaultConstructible {
+ public:
+ explicit NonDefaultConstructible(float v) : v(v) {}
+ bool operator==(const NonDefaultConstructible& other) const { return v == other.v; }
+ float v;
+};
+
+// Test constructing an error Result<T, E> for non-default-constructible T
+TEST(ResultNonDefaultConstructible, ConstructingError) {
+ Result<NonDefaultConstructible, int> result(std::make_unique<int>(placeholderError));
+ TestError(&result, placeholderError);
+}
+
+// Test moving an error Result<T, E> for non-default-constructible T
+TEST(ResultNonDefaultConstructible, MovingError) {
+ Result<NonDefaultConstructible, int> result(std::make_unique<int>(placeholderError));
+ Result<NonDefaultConstructible, int> movedResult(std::move(result));
+ TestError(&movedResult, placeholderError);
+}
+
+// Test returning an error Result<T, E> for non-default-constructible T
+TEST(ResultNonDefaultConstructible, ReturningError) {
+ auto CreateError = []() -> Result<NonDefaultConstructible, int> {
+ return {std::make_unique<int>(placeholderError)};
+ };
+
+ Result<NonDefaultConstructible, int> result = CreateError();
+ TestError(&result, placeholderError);
+}
+
+// Test constructing a success Result<T, E> for non-default-constructible T
+TEST(ResultNonDefaultConstructible, ConstructingSuccess) {
+ Result<NonDefaultConstructible, int> result(NonDefaultConstructible(1.0f));
+ TestSuccess(&result, NonDefaultConstructible(1.0f));
+}
+
+// Test moving a success Result<T, E> for non-default-constructible T
+TEST(ResultNonDefaultConstructible, MovingSuccess) {
+ Result<NonDefaultConstructible, int> result(NonDefaultConstructible(1.0f));
+ Result<NonDefaultConstructible, int> movedResult(std::move(result));
+ TestSuccess(&movedResult, NonDefaultConstructible(1.0f));
+}
+
+// Test returning a success Result<T, E> for non-default-constructible T
+TEST(ResultNonDefaultConstructible, ReturningSuccess) {
+ auto CreateSuccess = []() -> Result<NonDefaultConstructible, int> {
+ return {NonDefaultConstructible(1.0f)};
+ };
+
+ Result<NonDefaultConstructible, int> result = CreateSuccess();
+ TestSuccess(&result, NonDefaultConstructible(1.0f));
+}
+
} // anonymous namespace
} // namespace dawn