mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-16 00:17:03 +00:00
Add a functional-y Result<T, E> class and tests for it.
Implements two optimized versions of Result, one for Result<void, E*> as a nullable pointer, and one for Result<T*, E*> as a tagged pointer.
This commit is contained in:
committed by
Corentin Wallez
parent
2141e960b7
commit
44b5ca4aa1
@@ -25,6 +25,7 @@ list(APPEND COMMON_SOURCES
|
||||
${COMMON_DIR}/Math.cpp
|
||||
${COMMON_DIR}/Math.h
|
||||
${COMMON_DIR}/Platform.h
|
||||
${COMMON_DIR}/Result.h
|
||||
${COMMON_DIR}/Serial.h
|
||||
${COMMON_DIR}/SerialQueue.h
|
||||
${COMMON_DIR}/SwapChainUtils.h
|
||||
|
||||
250
src/common/Result.h
Normal file
250
src/common/Result.h
Normal file
@@ -0,0 +1,250 @@
|
||||
// Copyright 2018 The NXT Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef COMMON_RESULT_H_
|
||||
#define COMMON_RESULT_H_
|
||||
|
||||
#include "common/Assert.h"
|
||||
#include "common/Compiler.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
// Result<T, E> is the following sum type (Haskell notation):
|
||||
//
|
||||
// data Result T E = Success T | Error E | Empty
|
||||
//
|
||||
// It is meant to be used as the return type of functions that might fail. The reason for the Empty
|
||||
// case is that a Result should never be discarded, only destructured (its error or success moved
|
||||
// out) or moved into a different Result. The Empty case tags Results that have been moved out and
|
||||
// Result's destructor should ASSERT on it being Empty.
|
||||
//
|
||||
// Since C++ doesn't have efficient sum types for the special cases we care about, we provide
|
||||
// template specializations for them.
|
||||
|
||||
template <typename T, typename E>
|
||||
class Result;
|
||||
|
||||
// The interface of Result<T, E> shoud look like the following.
|
||||
// public:
|
||||
// Result(T success);
|
||||
// Result(E error);
|
||||
//
|
||||
// Result(Result<T, E>&& other);
|
||||
// Result<T, E>&& operator=(Result<T, E>&& other);
|
||||
//
|
||||
// ~Result();
|
||||
//
|
||||
// bool IsError() const;
|
||||
// bool IsSuccess() const;
|
||||
//
|
||||
// T&& AcquireSuccess();
|
||||
// E&& AcquireError();
|
||||
|
||||
// Specialization of Result for returning errors only via pointers. It is basically a pointer
|
||||
// where nullptr is both Success and Empty.
|
||||
template <typename E>
|
||||
class NXT_NO_DISCARD Result<void, E*> {
|
||||
public:
|
||||
Result();
|
||||
Result(E* error);
|
||||
|
||||
Result(Result<void, E*>&& other);
|
||||
Result<void, E*>& operator=(Result<void, E>&& other);
|
||||
|
||||
~Result();
|
||||
|
||||
bool IsError() const;
|
||||
bool IsSuccess() const;
|
||||
|
||||
void AcquireSuccess();
|
||||
E* AcquireError();
|
||||
|
||||
private:
|
||||
E* mError = nullptr;
|
||||
};
|
||||
|
||||
// Uses SFINAE to try to get alignof(T) but fallback to Default if T isn't defined.
|
||||
template <typename T, size_t Default, typename = size_t>
|
||||
constexpr size_t alignof_if_defined_else_default = Default;
|
||||
|
||||
template <typename T, size_t Default>
|
||||
constexpr size_t alignof_if_defined_else_default<T, Default, decltype(alignof(T))> = alignof(T);
|
||||
|
||||
// Specialization of Result when both the error an success are pointers. It is implemented as a
|
||||
// tagged pointer. The tag for Success is 0 so that returning the value is fastest.
|
||||
template <typename T, typename E>
|
||||
class NXT_NO_DISCARD Result<T*, E*> {
|
||||
public:
|
||||
static_assert(alignof_if_defined_else_default<T, 4> >= 4,
|
||||
"Result<T*, E*> reserves two bits for tagging pointers");
|
||||
static_assert(alignof_if_defined_else_default<E, 4> >= 4,
|
||||
"Result<T*, E*> reserves two bits for tagging pointers");
|
||||
|
||||
Result(T* success);
|
||||
Result(E* error);
|
||||
|
||||
Result(Result<T*, E*>&& other);
|
||||
Result<T*, E*>& operator=(Result<T*, E>&& other);
|
||||
|
||||
~Result();
|
||||
|
||||
bool IsError() const;
|
||||
bool IsSuccess() const;
|
||||
|
||||
T* AcquireSuccess();
|
||||
E* AcquireError();
|
||||
|
||||
private:
|
||||
enum PayloadType {
|
||||
Success = 0,
|
||||
Error = 1,
|
||||
Empty = 2,
|
||||
};
|
||||
|
||||
// Utility functions to manipulate the tagged pointer some of them don't need to be templated
|
||||
// but we really want them inlined so we keep them in templates
|
||||
static intptr_t MakePayload(void* pointer, PayloadType type);
|
||||
static PayloadType GetPayloadType(intptr_t payload);
|
||||
static T* GetSuccessFromPayload(intptr_t payload);
|
||||
static E* GetErrorFromPayload(intptr_t payload);
|
||||
|
||||
constexpr static intptr_t kEmptyPayload = Empty;
|
||||
intptr_t mPayload = kEmptyPayload;
|
||||
};
|
||||
|
||||
// Implementation of Result<void, E*>
|
||||
template <typename E>
|
||||
Result<void, E*>::Result() {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Result<void, E*>::Result(E* error) : mError(error) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Result<void, E*>::Result(Result<void, E*>&& other) : mError(other.mError) {
|
||||
other.mError = nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Result<void, E*>& Result<void, E*>::operator=(Result<void, E>&& other) {
|
||||
ASSERT(mError == nullptr);
|
||||
mError = other.mError;
|
||||
other.mError = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Result<void, E*>::~Result() {
|
||||
ASSERT(mError == nullptr);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool Result<void, E*>::IsError() const {
|
||||
return mError != nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool Result<void, E*>::IsSuccess() const {
|
||||
return mError == nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void Result<void, E*>::AcquireSuccess() {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
E* Result<void, E*>::AcquireError() {
|
||||
E* error = mError;
|
||||
mError = nullptr;
|
||||
return error;
|
||||
}
|
||||
|
||||
// Implementation of Result<T*, E*>
|
||||
template <typename T, typename E>
|
||||
Result<T*, E*>::Result(T* success) : mPayload(MakePayload(success, Success)) {
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
Result<T*, E*>::Result(E* error) : mPayload(MakePayload(error, Error)) {
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
Result<T*, E*>::Result(Result<T*, E*>&& other) : mPayload(other.mPayload) {
|
||||
other.mPayload = kEmptyPayload;
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
Result<T*, E*>& Result<T*, E*>::operator=(Result<T*, E>&& other) {
|
||||
ASSERT(mPayload == kEmptyPayload);
|
||||
mPayload = other.mPayload;
|
||||
other.mPayload = kEmptyPayload;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
Result<T*, E*>::~Result() {
|
||||
ASSERT(mPayload == kEmptyPayload);
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
bool Result<T*, E*>::IsError() const {
|
||||
return GetPayloadType(mPayload) == Error;
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
bool Result<T*, E*>::IsSuccess() const {
|
||||
return GetPayloadType(mPayload) == Success;
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
T* Result<T*, E*>::AcquireSuccess() {
|
||||
T* success = GetSuccessFromPayload(mPayload);
|
||||
mPayload = kEmptyPayload;
|
||||
return success;
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
E* Result<T*, E*>::AcquireError() {
|
||||
E* error = GetErrorFromPayload(mPayload);
|
||||
mPayload = kEmptyPayload;
|
||||
return error;
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
intptr_t Result<T*, E*>::MakePayload(void* pointer, PayloadType type) {
|
||||
intptr_t payload = reinterpret_cast<intptr_t>(pointer);
|
||||
ASSERT((payload & 3) == 0);
|
||||
return payload | type;
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
typename Result<T*, E*>::PayloadType Result<T*, E*>::GetPayloadType(intptr_t payload) {
|
||||
return static_cast<PayloadType>(payload & 3);
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
T* Result<T*, E*>::GetSuccessFromPayload(intptr_t payload) {
|
||||
ASSERT(GetPayloadType(payload) == Success);
|
||||
return reinterpret_cast<T*>(payload);
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
E* Result<T*, E*>::GetErrorFromPayload(intptr_t payload) {
|
||||
ASSERT(GetPayloadType(payload) == Error);
|
||||
return reinterpret_cast<E*>(payload ^ 1);
|
||||
}
|
||||
|
||||
#endif // COMMON_RESULT_H_
|
||||
Reference in New Issue
Block a user