Skip to content

Instantly share code, notes, and snippets.

@Naios
Last active July 10, 2016 12:57
Show Gist options
  • Save Naios/9579d3e85fc39016ad3cb1d50fcb40db to your computer and use it in GitHub Desktop.
Save Naios/9579d3e85fc39016ad3cb1d50fcb40db to your computer and use it in GitHub Desktop.
My implementation of Expected/ErrorOr
/**
* 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