Last active
July 10, 2016 12:57
-
-
Save Naios/9579d3e85fc39016ad3cb1d50fcb40db to your computer and use it in GitHub Desktop.
My implementation of Expected/ErrorOr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright 2016 Denis Blank <[email protected]> | |
* | |
* 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. | |
*/ | |
#include <system_error> | |
#include <tuple> | |
#include <type_traits> | |
#include <utility> | |
#include <memory> | |
#include <limits> | |
namespace detail { | |
using CopyAllocator = void (*)(void const *, void *); | |
using MoveAllocator = void (*)(void *, void *); | |
using Deallocator = void (*)(void *); | |
template <typename T> | |
static constexpr CopyAllocator CopyAllocatorOf() { | |
return [](void const *from, void *to) { | |
new (static_cast<T *>(to)) T(*static_cast<T const *>(from)); | |
}; | |
} | |
template <typename T> | |
static constexpr MoveAllocator MoveAllocatorOf() { | |
return [](void *from, void *to) { | |
new (static_cast<T *>(to)) T(std::move(*static_cast<T *>(from))); | |
}; | |
} | |
template <typename T> | |
static constexpr Deallocator DeallocatorOf() { | |
return [](void *ptr) { static_cast<T *>(ptr)->~T(); }; | |
} | |
template <typename T> | |
using IsCopyConstructibleAndAssignable = | |
std::integral_constant<bool, std::is_copy_constructible<T>::value && | |
std::is_copy_assignable<T>::value>; | |
template <typename... Rest> | |
struct AllCopyable; | |
template <typename First, typename... Rest> | |
struct AllCopyable<First, Rest...> | |
: std::conditional<IsCopyConstructibleAndAssignable<First>::value, | |
AllCopyable<Rest...>, std::false_type>::type {}; | |
template <> | |
struct AllCopyable<> : std::true_type {}; | |
template <std::size_t left, std::size_t right> | |
using StaticMax = | |
std::integral_constant<std::size_t, (left > right) ? left : right>; | |
template <std::size_t MaxSize, typename... Rest> | |
struct MaxSizeOfList; | |
template <std::size_t MaxSize, typename First, typename... Rest> | |
struct MaxSizeOfList<MaxSize, First, Rest...> | |
: MaxSizeOfList<StaticMax<MaxSize, sizeof(First)>::value, Rest...> {}; | |
template <std::size_t MaxSize> | |
struct MaxSizeOfList<MaxSize> : std::integral_constant<std::size_t, MaxSize> {}; | |
template <std::uint8_t Pos, typename T, typename... Rest> | |
struct PositionInList; | |
template <std::uint8_t Pos, typename T, typename... Rest> | |
struct PositionInList<Pos, T, T, Rest...> | |
: std::integral_constant<std::uint8_t, Pos> {}; | |
template <std::uint8_t Pos, typename T, typename First, typename... Rest> | |
struct PositionInList<Pos, T, First, Rest...> | |
: PositionInList<Pos + 1, T, Rest...> {}; | |
template <std::uint8_t Pos, typename T> | |
struct PositionInList<Pos, T> {}; | |
struct SkipConstructionTag {}; | |
template <typename... MyTypes> | |
class UniqueRawStorageUnion { | |
static_assert(sizeof...(MyTypes) < std::numeric_limits<std::uint8_t>::max(), | |
"Can't store that much types!"); | |
protected: | |
/// Is the greatest size of a type in the list | |
using MaxSizeOfListElement = MaxSizeOfList<0U, MyTypes...>; | |
// Evaluates to the type at the given position | |
template <std::uint8_t Pos> | |
using TypeAt = | |
decltype(std::get<Pos>(std::declval<std::tuple<MyTypes...>>())); | |
template <typename T> | |
using PositionOf = PositionInList<0U, T, MyTypes...>; | |
template <typename T> | |
using RequiresAvailableType = std::enable_if<PositionOf<T>::value >= 0>; | |
std::uint8_t marker_; | |
typename std::aligned_storage<MaxSizeOfListElement::value>::type storage_; | |
explicit UniqueRawStorageUnion(SkipConstructionTag) {} | |
public: | |
template <typename T, typename RequiresAvailableType<T>::type * = nullptr> | |
UniqueRawStorageUnion(T &&value) { | |
WeakAllocate<T>(std::forward<T>(value)); | |
} | |
UniqueRawStorageUnion(UniqueRawStorageUnion &&right) { | |
WeakMoveFrom(std::move(right)); | |
} | |
UniqueRawStorageUnion(UniqueRawStorageUnion const &) = delete; | |
~UniqueRawStorageUnion() { WeakDeallocate(); } | |
UniqueRawStorageUnion &operator=(UniqueRawStorageUnion &&right) { | |
WeakDeallocate(); | |
WeakMoveFrom(std::move(right)); | |
return *this; | |
} | |
UniqueRawStorageUnion &operator=(UniqueRawStorageUnion const &) = delete; | |
template <typename T, typename RequiresAvailableType<T>::type * = nullptr> | |
bool Is() const { | |
return marker_ == PositionOf<T>::value; | |
; | |
} | |
template <typename T, typename RequiresAvailableType<T>::type * = nullptr> | |
T *CastTo() { | |
CheckCastTo<T>(); | |
return UncheckedCastTo<T>(); | |
} | |
template <typename T, typename RequiresAvailableType<T>::type * = nullptr> | |
T const *CastTo() const { | |
CheckCastTo<T>(); | |
return UncheckedCastTo<T>(); | |
} | |
protected: | |
template <typename T> | |
void CheckCastTo() const { | |
assert(Is<T>() && "Illegal cast!"); | |
} | |
template <typename T> | |
T *UncheckedCastTo() { | |
return reinterpret_cast<T *>(&storage_); | |
} | |
template <typename T> | |
T const *UncheckedCastTo() const { | |
return reinterpret_cast<T const *>(&storage_); | |
} | |
template <typename T> | |
void SetTypeMarker() { | |
marker_ = PositionOf<T>(); | |
} | |
template <typename T, typename... Args> | |
void Allocate(Args &&... args) { | |
WeakDeallocate(); | |
WeakAllocate<T>(std::forward<Args>(args)...); | |
} | |
template <typename T, typename... Args> | |
void WeakAllocate(Args &&... args) { | |
SetTypeMarker<T>(); | |
new (UncheckedCastTo<T>()) T(std::forward<Args>(args)...); | |
} | |
void WeakMoveFrom(UniqueRawStorageUnion &&from) { | |
marker_ = from.marker_; | |
static MoveAllocator const table[] = {MoveAllocatorOf<MyTypes>()...}; | |
table[marker_](&from.storage_, &storage_); | |
} | |
void WeakDeallocate() { | |
static Deallocator const table[] = {DeallocatorOf<MyTypes>()...}; | |
table[marker_](&storage_); | |
} | |
}; | |
template <typename... MyTypes> | |
class CopyableRawStorageUnion : public UniqueRawStorageUnion<MyTypes...> { | |
public: | |
using UniqueRawStorageUnion<MyTypes...>::UniqueRawStorageUnion; | |
CopyableRawStorageUnion(CopyableRawStorageUnion &&) = default; | |
CopyableRawStorageUnion(CopyableRawStorageUnion const &right) | |
: UniqueRawStorageUnion<MyTypes...>(SkipConstructionTag{}) { | |
WeakCopyFrom(right); | |
} | |
CopyableRawStorageUnion &operator=(CopyableRawStorageUnion &&) = default; | |
CopyableRawStorageUnion &operator=(CopyableRawStorageUnion const &right) { | |
this->WeakDeallocate(); | |
WeakCopyFrom(right); | |
return *this; | |
} | |
protected: | |
void WeakCopyFrom(CopyableRawStorageUnion const &from) { | |
this->marker_ = from.marker_; | |
static CopyAllocator const table[] = {CopyAllocatorOf<MyTypes>()...}; | |
table[this->marker_](&from.storage_, &this->storage_); | |
} | |
}; | |
template <typename... MyTypes> | |
using RawStorageUnion = | |
typename std::conditional<AllCopyable<MyTypes...>::value, | |
CopyableRawStorageUnion<MyTypes...>, | |
UniqueRawStorageUnion<MyTypes...>>::type; | |
struct VoidTag {}; | |
using ErrorType = std::error_code; | |
template <typename T> | |
class ExpectedBase { | |
protected: | |
RawStorageUnion<ErrorType, T> storage_; | |
explicit ExpectedBase(T value) : storage_(std::move(value)) {} | |
public: | |
explicit ExpectedBase(std::error_code error) : storage_(std::move(error)) {} | |
ExpectedBase &operator=(std::error_code error) { | |
storage_ = std::move(error); | |
return *this; | |
} | |
}; | |
template <typename T> | |
class ExpectedConstructorBase : public ExpectedBase<T> { | |
public: | |
ExpectedConstructorBase(std::error_code error) | |
: ExpectedBase<T>(std::move(error)) {} | |
ExpectedConstructorBase(T value) : ExpectedBase<T>(std::move(value)) {} | |
ExpectedConstructorBase &operator=(T value) { | |
this->storage_ = std::move(value); | |
return *this; | |
} | |
bool IsValue() const { return this->storage_.template Is<T>(); } | |
T &operator*() { return *(this->storage_.template CastTo<T>()); } | |
T const &operator*() const { return *(this->storage_.template CastTo<T>()); } | |
T &operator->() { return *(this->storage_.template CastTo<T>()); } | |
T const &operator->() const { return *(this->storage_.template CastTo<T>()); } | |
}; | |
template <> | |
class ExpectedConstructorBase<void> : public ExpectedBase<VoidTag> { | |
public: | |
ExpectedConstructorBase(std::error_code error) | |
: ExpectedBase<VoidTag>(std::move(error)) {} | |
ExpectedConstructorBase() : ExpectedBase<VoidTag>(VoidTag{}) {} | |
bool IsValue() const { return this->storage_.Is<VoidTag>(); } | |
}; | |
} // namespace detail | |
/// Represents a type which contains an optional value or | |
/// a `std::error_code` which indicates why the action failed, | |
/// when there is no value present. | |
/// | |
/// This class is used as exception less alternative for returning errors | |
/// by the library. An important method is the Expected::operator bool(), | |
/// which returns true when there is a value present: | |
/// | |
/// if (Expected<int> result = library->GetResult()) | |
/// DoSomething(*result); | |
/// else | |
/// DoSomethingElse(result.GetError()); | |
/// | |
/// The Expected type also offers multiple const and no const operator overloads | |
/// to get the result like: Expected::operator* () or Expected::operator-> (). | |
template <typename T = void> | |
class Expected : public detail::ExpectedConstructorBase<T> { | |
static_assert(std::is_same<T, typename std::decay<T>::type>::value, | |
"Requires a decayed type!"); | |
template <typename O> | |
static std::error_code ConvertToError(Expected<O> error) { | |
assert(error.IsError() && "Tried to convert from a value to an error!"); | |
return error.GetError(); | |
} | |
public: | |
using detail::ExpectedConstructorBase<T>::ExpectedConstructorBase; | |
Expected() = default; | |
template <typename O> | |
Expected(Expected<O> error) | |
: detail::ExpectedConstructorBase<T>(ConvertToError(std::move(error))) {} | |
bool IsError() const { | |
return this->storage_.template Is<detail::ErrorType>(); | |
} | |
explicit operator bool() const { return !IsError(); } | |
detail::ErrorType const &GetError() const { | |
return *(this->storage_.template CastTo<detail::ErrorType>()); | |
} | |
}; | |
int main() | |
{ | |
Expected<> r0; | |
Expected<int> r1(0); | |
Expected<int> r2(std::error_code{}); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment