Giter VIP home page Giter VIP logo

expected's Introduction

expected

Single header std::expected implementation using C++20.

The library implements these 2 proposals:

  • P0323 defines the basic interface for std::expected.
  • P2505 defines the monadic inteface extensions for std::expected.

This implementation provides expected in namespace rd. So, for any use of std::expected, use rd::expected.

What std::expected does

std::expected is a mechanism for error handling. This is very similar to rust's std::result. std::expected basically contains one of Result or Error.

Why not just use exceptions

There are probably 2 reasons for not "always" using exceptions:

  • Exceptions are forbidden in many codebases
  • Exceptions are too costly when any exception case arise.

While writing any library, this poses a big problem for error handling. In library, if we choose to throw exception on error condition, we are making the choice for library user. Error condition in library can or can not be an exceptional situation in context of the application that uses that library.

std::expected provides a standard alternative for exception that can be used for error condition that are not exceptional in context of application.

While saying this, this should be noted that exceptions are great for panic conditions. And there exists many obvious panic conditions, for which only exceptions should be used.

What about other alternatives

error_code used to be simplest error handling mechanism. Historically, error_code is a simple integer, that is not good for rich error context.

Nowadays, C++ header <system_error> provides richer error code. However, error_code leads to monopolization of return channel. And thus value need to be outputted with some other channel.

It also forces you to write if condition after each function call.

Never empty guarantee

std::expected guarantees that it is never going to be empty. i.e., it would either contain one of value or error at any point of time. This makes it really easy to use.

Simple code with rd::expected

auto get_file_handle(std::string_view path) -> rd::expected<file, fail_reason>;

auto read_file(file f) -> rd::expected<std::string, fail_reason>;

auto to_int(std::string str) -> rd::expected<int, fail_reason>;

auto increment(int x) -> int;

auto read_int_with_increment(std::string_view path) -> rd::expected<int, fail_reason>{
  return get_file_handle(path)
         .and_then(read_file)
	 .and_then(to_int)
	 .transform(increment);
}

Documentation

rd::expected

template <typename T, typename E>
class expected

T, E can't be a reference type, as they are not supported in paper too.

Member types

using value_type = T;
using error_type = E;
using unexpected_type = unexpected<E>;

template <class U>
using rebind = expected<U, error_type>;

Constructors

Default constructor:

constexpr expected();

Copy constructor:

constexpr expected(expected const&);

Move constructor:

constexpr expected(expected &&);

Expected constructor:

U should be convertible to T, and G should be convertible to E.

template <class U, class G>
constexpr expected(expected<U, G> const&);

template <class U, class G>
constexpr expected(expected<U, G> &&);

Value constructor:

Constructor for expected \<T , E> that accepts value to initialize T(value part of expected).

template <class U = T>
constexpr expected(U&& v);

Unexpected constructor:

Constructor for expected<T, E> that accepts rd::unexpected instance to initialize E(error part of expected).

template <class G>
constexpr expected(unexpected<G> const&);

template <class G>
constexpr expected(unexpected<G> &&);

in_place_t constructor:

Constructor for expected<T, E> that accepts in_place tag and arguments(to be forwarded) to initialize T.

template <class... Args>
constexpr expected(std::in_place_t, Args&&... args);

template <class U, class... Args>
constexpr expected(std::in_place_t, std::initializer_list<U>, Args&&... args);

unexpect_t constructor:

Constructor for expected<T, E> that accepts rd::unexpect tag and and arguments(to be forwarded) to initialize E.

template <class... Args>
constexpr expected(rd::unexpect_t, Args&&... args);

template <class G, class... Args>
constexpr expected(rd::unexpect_t, std::initializer_list<G>, Args&&... args);

Assignment

copy assignment:

constexpr expected& operator=(expected const&);

move assignment:

constexpr expected& operator=(expected &&);

Value assignment:

Assigns value to expected

template <class U = T>
constexpr expected& operator=(U&& rhs);

rd::unexpected assignment:

Assigns error to expected

template <class G>
constexpr expected& operator=(unexpected<G> const&);

template <class G>
constexpr expected& operator=(unexpected<G> &&);

Modifiers

emplace:

Accepts args to constructor new value for expected and assign to it.

template <class... Args>
constexpr T& emplace(Args&&... args);

template <class U, class... Args>
constexpr T& emplace(std::initializer_list<U>, Args&&...);

Swap

swap member function and friend function

Observers

// For accessing T's members

// precondition: has_value() == true
constexpr T const* operator->() const noexcept;

// precondition: has_value() == true
constexpr T* operator->() noexcept;
// Getting reference to T

// precondition: has_value() == true
constexpr T const& operator*() const& noexcept;

// precondition: has_value() == true
constexpr T& operator*() & noexcept;

// precondition: has_value() == true
constexpr T&& operator*() && noexcept;

// precondition: has_value() == true
constexpr T const&& operator*() const&& noexcept;
// Query if value exists

constexpr explicit operator bool() const noexcept;

constexpr bool has_value() const noexcept;
// Get value, if not exists throws exception rd::bad_expected_access(error())

constexpr T const& value() const&;
constexpr T& value() &;
constexpr T&& value() &&;
constexpr T const&& value() const&&;
// Get error, (undefined behavior if error not exists)

constexpr E const& error() const&;
constexpr E& error() &;
constexpr E&& error() &&;
constexpr E const&& error() const&&;
// Get the value, if value doesn't exist return v

template <class U>
constexpr T value_or(U&& v) const&;

template <class U>
constexpr T value_or(U&& v) &&;

Monadic

and_then:

F should be invocable with value type of expected and should return expected whose error type should be E. It returns error of current expected, if error is there, other returns the result of invoking f with value.

template <class F>
constexpr auto and_then(F&& f) &;

template <class F>
constexpr auto and_then(F&& f) &&;

template <class F>
constexpr auto and_then(F&& f) const &;

template <class F>
constexpr auto and_then(F&& f) const &&;

or_else:

F should be invocable with error type of expected and should return expected whose value type should be T.

If invoke_result_t<F, E> is any specialization of expected (whose value type should be same as T), then if a value is there, value is returned wrapped in invoke_result_t of F otherwise returns the result of invoking f.

If invoke_result_t<F, E> is void, then, if error is there, then F is invoked with error. Current expected is returned as result.

template <class F>
constexpr auto or_else(F&& f) &;

template <class F>
constexpr auto or_else(F&& f) &&;

template <class F>
constexpr auto or_else(F&& f) const &;

template <class F>
constexpr auto or_else(F&& f) const &&;

transform:

F should be invocable with value type of current expected. If current expected contains error the resultant expected contains that error otherwise contains the result of invocation of current expected value with F.

template <class F>
constexpr auto transform(F&& f) &;

template <class F>
constexpr auto transform(F&& f) &&;

template <class F>
constexpr auto transform(F&& f) const &;

template <class F>
constexpr auto transform(F&& f) const &&;

transform_error:

F should be invocable with error type of current expected. If current expected contains value the resultant expected contains that value otherwise contains the result of invocation of current expected error with F.

template <class F>
constexpr auto transform_error(F&& f) &;

template <class F>
constexpr auto transform_error(F&& f) &&;

template <class F>
constexpr auto transform_error(F&& f) const &;

template <class F>
constexpr auto transform_error(F&& f) const &&;

Equality

template <class U, class G>
constexpr friend bool operator==(expected const&, expected<U, G> const&);

template <class U>
constexpr friend bool operator==(expected const&, U const&);

template <class G>
constexpr friend bool operator==(expected const&, rd::unexpected<G> const&);

rd::unexpected

template <typename T>
class unexpected

Member types

using value_type = T

Constructors

Copy constructor:

constexpr unexpected(unexpected const&);

Move constructor:

constexpr unexpected(unexpected&&);

in_place_t constructor:

It accepts a std::in_place tag and constructs T with args.

template <typename ...Args>
constexpr unexpected(std::in_place_t, Args&&... args);

template <typename U, typename ...Args>
constexpr unexpected(std::in_place_t, std::initializer_list<U>, Args&&... args);

value constructor:

Constructs T with err.

template <typename Err>
constexpr unexpected(Err&& err);

Observer

// value(): gets the stored T value

constexpr T & value() &;
constexpr T const& value() const&;
constexpr T && value() &&;
constexpr T const&& value() const&&;

Equality operator

template <class E2>
friend constexpr bool operator==(unexpected const& x, unexpected<E2> const& y);

rd::bad_expected_access

bad_expected_access derives from exception. Stores the error value.

template<class E>
class bad_expected_access;
E& error() & noexcept;
E const& error() const& noexcept;
E&& error() && noexcept;
E const&& error() const&& noexcept;

rd::unexpect_t

This is just a tag type, to signify constructing error for expected.

Library also exposes a unexpect variable of type rd::unexpect_t.

inline constexpr unexpect_t unexpect{};

TODO

  • Improve Documentation
  • Have a conan package
  • Add some good examples

expected's People

Contributors

rishabhrd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

expected's Issues

Some compilation problems

std::convertible_to evaluates to false for same types of rd::expected<void, E>

Checked on:
msvc (cl 19.35.32215) /std:c++20
clang (15.0.0) -std=c++20

Some code to explain below:
godbolt clang 15 - issue 1

#include <concepts>
#include <system_error>
#include <rd/expected.hpp>

template <typename T>
using result = rd::expected<T, std::error_code>;

static_assert(std::convertible_to<result<std::size_t>, result<int>>); // successfully, ok
static_assert(std::convertible_to<result<void>, result<void>>); // failure, why? 

Second assertion failed, Why can't identical rd::expected types be converted to each other? Is there a semantic reason for this behavior?

It may be related to another issue:
godbolt clang 15 - issue 2

#include <system_error>
#include <concepts>
#include <span>
#include <variant>
#include <rd/expected.hpp>

template <typename T>
using result = rd::expected<T, std::error_code>;

template <typename T>
using raw_result = std::variant<T, rd::unexpected<std::error_code>>;

struct data_header { 
    std::size_t data_length; 
    static raw_result<data_header> from_buffer(std::span<std::byte> buffer) noexcept; // compile
};

struct tpkt_header { 
    std::size_t length; 
    static result<tpkt_header> from_buffer(std::span<std::byte> buffer) noexcept; // won't compile
};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.