Remove expected

This commit is contained in:
Peter Dimov
2019-05-12 18:56:54 +03:00
parent 6390b5ed20
commit 3041fff85c
2 changed files with 0 additions and 751 deletions

View File

@ -1,305 +0,0 @@
# expected<T, E...>
## Description
The class `expected<T, E...>` presented here is an extended version of `expected<T, E>` as
proposed in [P0323R1](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0323r1.pdf)
and the subsequent [D0323R2](https://github.com/viboes/std-make/blob/master/doc/proposal/expected/d0323r2.md).
The main difference is that this class takes more than one error type, which makes it more
flexible. One example of a type of the `expected` family, [`outcome<T>`](https://ned14.github.io/boost.outcome/),
on failure can store either an error of type `std::error_code`, or an exception in the form of `std::exception_ptr`.
This can be represented naturally in this implementation via `expected<T, std::error_code, std::exception_ptr>`.
In addition, libraries would generally differ in their choice of error types. It would be a
common need in practice of having to combine the results of calling two different libraries,
each with its own error type. Library 1 may use `lib1::error`:
namespace lib1
{
enum class error
{
division_by_zero,
other_error
};
expected<double, error> div( double x, double y );
} // namespace lib1
while Library 2 might define its own `lib2::error`:
namespace lib2
{
enum class error
{
division_by_zero,
negative_logarithm
};
expected<double, error> log( double x );
} // namespace lib2
In this proposal, combining the results of `lib1::div` and `lib2::log` can be achieved via
simple composition:
expected<double, lib1::error, lib2::error> log_div_mul( double x, double y, double m )
{
auto r1 = lib1::div( x, y );
if( !r1 ) return r1.unexpected();
auto r2 = lib2::log( r1.value() );
if( !r2 ) return r2.unexpected();
return m * r2.value();
}
An alternative approach that requires more effort is also supported:
enum class common_error
{
division_by_zero,
negative_logarithm,
other_error,
unknown_error
};
common_error make_common_error( lib1::error e );
common_error make_common_error( lib2::error e );
expected<double, common_error> log_div_mul2( double x, double y, double m )
{
static const auto rm = []( auto x ) { return make_common_error(x); };
auto r1 = lib1::div( x, y ).remap_errors( rm );
if( !r1 ) return r1.unexpected();
auto r2 = lib2::log( r1.value() ).remap_errors( rm );
if( !r2 ) return r2.unexpected();
return m * r2.value();
}
`std::error_code` is a very good choice for a common error type, and it's supported
natively by the overload of `.remap_errors()` that takes no arguments, which uses
calls to `make_error_code` to translate the errors.
When an attempt to access the value via `r.value()` is made and an error is present,
an exception is thrown. By default, this exception is of type `bad_expected_access<E>`,
as in D0323R2, but there are two differences. First, `bad_expected_access<E>` objects
derive from a common base `bad_expected_access<void>` so that they can be caught at
points where the set of possible `E` is unknown.
Second, the thrown exception can be customized. The implementation calls
`throw_on_unexpected(e)` unqualified, where `e` is the error object, and the user can
define such a function in the namespace of the type of `e`. Two specialized overloads
of `throw_on_unexpected` are provided, one for `std::error_code`, which throws the
corresponding `std::system_error`, and one for `std::exception_ptr`, which rethrows
the exception stored in it.
For example, `lib1` from above may customize the exceptions associated with `lib1::error`
via the following:
namespace lib1
{
enum class error
{
division_by_zero,
other_error
};
class exception: public std::exception
{
private:
error e_;
public:
explicit exception( error e ): e_( e ) {}
virtual const char * what() const noexcept;
};
void throw_on_unexpected( error e )
{
throw exception( e );
}
} // namespace lib1
In this implementation, `unexpected_type<E...>` has been called `unexpected_<T...>` and is
an alias for `variant<T...>`. It is unfortunately not possible to use the name `unexpected<T...>`,
because a function `std::unexpected` already exists.
The `make_...` helper functions have been omitted as unnecessary; class template argument deduction
as in `expected{ 1.0 }` or `unexpected_{ lib1::division_by_zero }` suffices.
Other functions have also been dropped as unnecessary, not providing sufficient value, dangerous, or
a combination of the three, although the decision of what to include isn't final at this point. The aim
is to produce a minimal interface that still covers the use cases.
`expected<T, E1...>` can be converted to `expected<T, E2...>` if all error types in `E1...` are
also in `E2...`. This allows composition as in the example above. Whether value convertibility ought
to also be supported is an open question.
A single monadic operation ("bind") is supported in the form of `operator>>`, allowing
auto log_div_mul3( double x, double y, double m )
{
return lib1::div( x, y ) >> [&]( auto && r1 ) {
return lib2::log( r1 ) >> [&]( auto && r2 ) -> expected<double, lib1::error, lib2::error> {
return m * r2;
};
};
}
as well as the more concise in this example, although limited in utility for real world scenarios,
auto log_div_mul3( double x, double y, double m )
{
return lib1::div( x, y ) >> std::bind<expected<double, lib1::error, lib2::error>>( lib2::log, _1 ) >> m * _1;
}
The more traditional name `then` was also a candidate for this operation, but `operator>>` has two advantages;
it avoids the inevitable naming debates and does not require parentheses around the continuation lambda.
## Synopsis
// unexpected_
template<class... E> using unexpected_ = variant<E...>;
// bad_expected_access
template<class E = void> class bad_expected_access;
template<> class bad_expected_access<void>: public std::exception
{
public:
bad_expected_access() noexcept;
char const * what() const noexcept;
};
template<class E> class bad_expected_access: public bad_expected_access<void>
{
public:
explicit bad_expected_access( E const& e );
E error() const;
};
// throw_on_unexpected
template<class E> void throw_on_unexpected( E const& e );
void throw_on_unexpected( std::error_code const& e );
void throw_on_unexpected( std::exception_ptr const& e );
// expected
template<class T, class... E> class expected
{
public:
// value constructors
constexpr expected() noexcept( /*see below*/ );
constexpr expected( T const& t ) noexcept( /*see below*/ );
constexpr expected( T&& t ) noexcept( /*see below*/ );
// unexpected constructor
template<class... E2>
constexpr expected( unexpected_<E2...> const& x );
template<class... E2>
constexpr expected( unexpected_<E2...>&& x );
// conversion constructor
template<class... E2>
constexpr expected( expected<T, E2...> const& x );
template<class... E2>
constexpr expected( expected<T, E2...>&& x );
// emplace
template<class... A> void emplace( A&&... a );
template<class V, class... A> void emplace( std::initializer_list<V> il, A&&... a );
// swap
void swap( expected& r ) noexcept( /*see below*/ );
// value queries
constexpr bool has_value() const noexcept;
constexpr explicit operator bool() const noexcept;
// checked value access
constexpr T& value() &;
constexpr T const& value() const&;
constexpr T&& value() &&;
constexpr T const&& value() const&&;
// unchecked value access
T* operator->() noexcept;
T const* operator->() const noexcept;
T& operator*() & noexcept;
T const& operator*() const & noexcept;
T&& operator*() && noexcept;
T const&& operator*() const && noexcept;
// error queries
template<class E2> constexpr bool has_error() const noexcept;
constexpr bool has_error() const noexcept;
// error access
unexpected_<E...> unexpected() const;
template<class E2> constexpr E2 error() const noexcept;
constexpr /*see below*/ error() const noexcept;
// error mapping
template<class F> /*see below*/ remap_errors( F&& f ) const;
expected<T, std::error_code> remap_errors() const;
// then
template<class F> /*see below*/ operator>>( F&& f ) const;
};
template<class T, class... E>
inline constexpr bool operator==( expected<T, E...> const& x1, expected<T, E...> const& x2 );
template<class T, class... E>
inline constexpr bool operator!=( expected<T, E...> const& x1, expected<T, E...> const& x2 );
template<class T, class... E>
inline void swap( expected<T, E...>& x1, expected<T, E...>& x2 ) noexcept( /*see below*/ );
// is_expected
template<class T> struct is_expected;
} // namespace variant2
} // namespace boost
## Reference
...

View File

@ -1,446 +0,0 @@
#ifndef BOOST_VARIANT2_EXPECTED_HPP_INCLUDED
#define BOOST_VARIANT2_EXPECTED_HPP_INCLUDED
// Copyright 2017 Peter Dimov.
//
// Distributed under the Boost Software License, Version 1.0.
//
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
#ifndef BOOST_VARIANT2_VARIANT_HPP_INCLUDED
#include <boost/variant2/variant.hpp>
#endif
#include <boost/core/demangle.hpp>
#include <system_error>
#include <type_traits>
#include <typeinfo>
#include <cassert>
//
namespace boost
{
namespace variant2
{
// unexpected_
template<class... E> using unexpected_ = variant<E...>;
// bad_expected_access
template<class E = void> class bad_expected_access;
template<> class bad_expected_access<void>: public std::exception
{
private:
std::string msg_;
public:
bad_expected_access() noexcept
{
}
explicit bad_expected_access( std::string&& msg ) noexcept: msg_( std::move(msg) ) // extension
{
}
char const * what() const noexcept
{
return msg_.empty()? "bad_expected_access<>": msg_.c_str();
}
};
namespace detail
{
template<class E, class En = std::enable_if_t<!std::is_enum<E>::value>> std::string add_value( E const& /*e*/ )
{
return std::string();
}
template<class E, class E1 = void, class E2 = std::enable_if_t<std::is_enum<E>::value>> std::string add_value( E const& e )
{
return ": " + std::to_string( static_cast<int>(e) );
}
} // namespace detail
template<class E> class bad_expected_access: public bad_expected_access<void>
{
private:
E e_;
public:
explicit bad_expected_access( E const& e )
: bad_expected_access<void>( "bad_expected_access<" + boost::core::demangle( typeid(E).name() ) + ">" + variant2::detail::add_value( e ) ), e_( e )
{
}
E error() const
{
return e_;
}
};
// throw_on_unexpected
template<class E> void throw_on_unexpected( E const& /*e*/ )
{
}
void throw_on_unexpected( std::error_code const & e )
{
throw std::system_error( e );
}
void throw_on_unexpected( std::exception_ptr const & e )
{
if( e )
{
std::rethrow_exception( e );
}
else
{
throw bad_expected_access<>( "bad_expected_access<>: null exception_ptr" );
}
}
// expected
template<class T, class... E> class expected;
template<class T> struct is_expected: std::false_type {};
template<class T, class... E> struct is_expected<expected<T, E...>>: std::true_type {};
template<class T, class... E> class expected
{
private:
variant<T, E...> v_;
private:
void _bad_access() const
{
mp_with_index<mp_size<expected>>( v_.index(), [&]( auto I )
{
if( I == 0 )
{
throw bad_expected_access<>( "bad_expected_access<>: value present on error request" );
}
else
{
auto const & e = get<I>(v_);
throw_on_unexpected( e );
throw bad_expected_access<std::decay_t<decltype(e)>>( e );
}
});
}
public:
// value constructors
constexpr expected() noexcept( std::is_nothrow_default_constructible<T>::value )
{
}
constexpr expected( T const& t ) noexcept( std::is_nothrow_copy_constructible<T>::value ): v_( in_place_index<0>, t )
{
}
constexpr expected( T && t ) noexcept( std::is_nothrow_move_constructible<T>::value ): v_( in_place_index<0>, std::move(t) )
{
}
// template<class U> constexpr expected( U && u ); where U in E...?
// in-place constructor?
// unexpected constructor
template<class... E2,
class En = mp_if<mp_all<std::is_copy_constructible<E2>..., mp_contains<mp_list<E...>, E2>...>, void>>
constexpr expected( unexpected_<E2...> const & x ): v_( x )
{
}
template<class... E2,
class En = mp_if<mp_all<std::is_move_constructible<E2>..., mp_contains<mp_list<E...>, E2>...>, void>>
constexpr expected( unexpected_<E2...> && x ): v_( std::move(x) )
{
}
// conversion constructor
template<class... E2,
class En = mp_if<mp_all<std::is_copy_constructible<E2>..., mp_contains<mp_list<E...>, E2>...>, void>>
constexpr expected( expected<T, E2...> const & x ): v_( x.v_ )
{
}
template<class... E2,
class En = mp_if<mp_all<std::is_move_constructible<E2>..., mp_contains<mp_list<E...>, E2>...>, void>>
constexpr expected( expected<T, E2...> && x ): v_( std::move(x.v_) )
{
}
// emplace
template<class... A> void emplace( A&&... a )
{
v_.emplace( std::forward<A>(a)... );
}
template<class V, class... A> void emplace( std::initializer_list<V> il, A&&... a )
{
v_.emplace( il, std::forward<A>(a)... );
}
// swap
void swap( expected & r ) noexcept( noexcept( v_.swap( r.v_ ) ) )
{
v_.swap( r.v_ );
}
// value queries
constexpr bool has_value() const noexcept
{
return v_.index() == 0;
}
constexpr explicit operator bool() const noexcept
{
return v_.index() == 0;
}
// checked value access
constexpr T& value() &
{
if( !has_value() )
{
_bad_access();
}
return *get_if<0>(&v_);
}
constexpr T const& value() const&
{
if( !has_value() )
{
_bad_access();
}
return *get_if<0>(&v_);
}
constexpr T&& value() &&
{
return std::move( value() );
}
constexpr T const&& value() const&&
{
return std::move( value() );
}
// unchecked value access
T* operator->() noexcept
{
return get_if<0>(&v_);
}
T const* operator->() const noexcept
{
return get_if<0>(&v_);
}
T& operator*() & noexcept
{
T* p = get_if<0>(&v_);
assert( p != 0 );
return *p;
}
T const& operator*() const & noexcept
{
T const* p = get_if<0>(&v_);
assert( p != 0 );
return *p;
}
T&& operator*() && noexcept
{
return std::move(**this);
}
T const&& operator*() const && noexcept
{
return std::move(**this);
}
// error queries
template<class E2> constexpr bool has_error() const noexcept
{
using I = mp_find<expected, E2>;
return v_.index() == I::value;
}
constexpr bool has_error() const noexcept
{
static_assert( sizeof...(E) == 1, "has_error() is only valid when there is a single E" );
return has_error<mp_first<expected>>();
}
// error access
unexpected_<E...> unexpected() const
{
if( has_value() )
{
_bad_access();
}
return v_.template subset<E...>();
}
template<class E2> constexpr E2 error() const noexcept
{
using I = mp_find<expected, E2>;
if( v_.index() != I::value )
{
_bad_access();
}
return get<I>( v_ );
}
constexpr mp_first<expected> error() const noexcept
{
static_assert( sizeof...(E) == 1, "error() is only valid when there is a single E" );
return error<mp_first<expected>>();
}
// error mapping
private:
template<class F> struct Qret
{
template<class... A> using fn = decltype( std::declval<F>()( std::declval<A>()... ) );
};
template<class F> using remapped = mp_append<expected<T>, mp_unique<mp_transform_q<Qret<F>, mp_list<E...>>>>;
template<class R, std::size_t I, class F, class V> static R _remap_error( mp_size_t<I>, F && f, V && v )
{
// return R( std::forward<F>(f)( std::forward<V>(v) ) );
auto e = std::forward<F>(f)( std::forward<V>(v) );
return unexpected_<decltype(e)>{ e };
}
template<class R, class F, class V> static R _remap_error( mp_size_t<0>, F && /*f*/, V && v )
{
return R( std::forward<V>(v) );
}
public:
template<class F> remapped<F> remap_errors( F && f ) const
{
using R = remapped<F>;
return mp_with_index<mp_size<expected>>( v_.index(), [&]( auto I ) {
return this->_remap_error<R>( I, f, get<I>(v_) );
});
}
expected<T, std::error_code> remap_errors() const
{
using R = expected<T, std::error_code>;
auto f = []( auto const& e ){ return make_error_code(e); };
return mp_with_index<mp_size<expected>>( v_.index(), [&]( auto I ) {
return this->_remap_error<R>( I, f, get<I>(v_) );
});
}
// then
private:
template<class F, class U> using then_result_ = decltype( std::declval<F>()( std::declval<U>() ) );
template<class F, class U, class R = then_result_<F, U>> using then_result = mp_if<is_expected<R>, R, expected<R, E...>>;
public:
template<class F> then_result<F, T const&> then( F && f ) const
{
if( has_value() )
{
return std::forward<F>(f)( **this );
}
else
{
return unexpected();
}
}
template<class F> then_result<F, T const&> operator>>( F && f ) const
{
if( has_value() )
{
return std::forward<F>(f)( **this );
}
else
{
return unexpected();
}
}
};
template<class T, class... E> inline constexpr bool operator==( expected<T, E...> const & x1, expected<T, E...> const & x2 )
{
return x1.v_ == x2.v_;
}
template<class T, class... E> inline constexpr bool operator!=( expected<T, E...> const & x1, expected<T, E...> const & x2 )
{
return x1.v_ != x2.v_;
}
template<class T, class... E> inline void swap( expected<T, E...> & x1, expected<T, E...> & x2 ) noexcept( noexcept( x1.swap( x2 ) ) )
{
x1.swap( x2 );
}
} // namespace variant2
} // namespace boost
#endif // #ifndef BOOST_VARIANT2_EXPECTED_HPP_INCLUDED