From 0c7d573af8e9a30dc5b51d3e4d2a487ce96dc242 Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 3 Jun 2017 14:24:41 +0300 Subject: [PATCH 01/11] Update README.md --- README.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 972145e..d28459b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,30 @@ # variant2 -A never-valueless C++14 implementation of [std::variant](http://en.cppreference.com/w/cpp/utility/variant). Requires [mp11](https://github.com/pdimov/mp11) and Boost.Config. Intended to be placed into the `libs/variant2` directory of a Boost clone or release, with mp11 in `libs/mp11`. +This repository contains a never-valueless C++14 implementation of [std::variant](http://en.cppreference.com/w/cpp/utility/variant) in [variant.hpp](include/boost/variant2/variant.hpp) and an implementation of `expected` in [expected.hpp](include/boost/variant2/variant.hpp) that is an extended version of `extended` as proposed in [P0323R1](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0323r1.pdf). -To avoid the valueless state, this implementation falls back to using double storage unless - -* all the contained types are nothrow move constructible, or -* at least one contained type has a nothrow default constructor. +The code requires [mp11](https://github.com/pdimov/mp11) and Boost.Config. The repository is intended to be placed into the `libs/variant2` directory of a Boost clone or release, with mp11 in `libs/mp11`, but the headers will also work standalone if [mp11.hpp](https://github.com/pdimov/mp11/blob/master/include/boost/mp11.hpp) or [mp11_single.hpp](https://github.com/pdimov/mp11/blob/master/include/boost/mp11_single.hpp) is included beforehand. Supported compilers: * g++ 5 or later with -std=c++14 or -std=c++1z * clang++ 3.5 or later with -std=c++14 or -std=c++1z * Visual Studio 2017 + +## variant.hpp + +The class `boost::variant2::variant` is an almost conforming implementation of `std::variant` with the following differences: + +* The function `valueless_by_exception()` is not present, as the variant is never valueless; +* A converting constructor from, e.g. `variant` to `variant` is provided as an extension; +* The reverse operation, going from `variant` to `variant` is provided as the member function `subset`. (This operation can throw if the current state of the variant cannot be represented.) + +To avoid the valueless state, this implementation falls back to using double storage unless + +* all the contained types are nothrow move constructible, or +* at least one contained type has a nothrow default constructor. + +If the second bullet doesn't hold, but the first does, the variant uses single storage, but `emplace` constructs a temporary and moves it into place if the construction of the object can throw. In case this is undesirable, the recommended practice is to add an alternative that has a nonthrowing default constructor. + +## expected.hpp + +The class `boost::variant2::expected` represents the return type of an operation that may potentially fail. It contains either the expected result of type `T`, or a reason for the failure of one of the error types in `E...`. Internally, this is represented as `variant`. From d5d5af660ed608715f74521483e8d79a18b97892 Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 3 Jun 2017 14:26:24 +0300 Subject: [PATCH 02/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d28459b..182be6f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # variant2 -This repository contains a never-valueless C++14 implementation of [std::variant](http://en.cppreference.com/w/cpp/utility/variant) in [variant.hpp](include/boost/variant2/variant.hpp) and an implementation of `expected` in [expected.hpp](include/boost/variant2/variant.hpp) that is an extended version of `extended` as proposed in [P0323R1](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0323r1.pdf). +This repository contains a never-valueless C++14 implementation of [std::variant](http://en.cppreference.com/w/cpp/utility/variant) in [variant.hpp](include/boost/variant2/variant.hpp) and an implementation of `expected` in [expected.hpp](include/boost/variant2/variant.hpp) that is an extended version of `extended` 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 code requires [mp11](https://github.com/pdimov/mp11) and Boost.Config. The repository is intended to be placed into the `libs/variant2` directory of a Boost clone or release, with mp11 in `libs/mp11`, but the headers will also work standalone if [mp11.hpp](https://github.com/pdimov/mp11/blob/master/include/boost/mp11.hpp) or [mp11_single.hpp](https://github.com/pdimov/mp11/blob/master/include/boost/mp11_single.hpp) is included beforehand. From 4e34749d87c0fd3113631c0dfe76d2d61f8988be Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 3 Jun 2017 14:43:21 +0300 Subject: [PATCH 03/11] Add doc/expected.md --- doc/expected.md | 131 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 doc/expected.md diff --git a/doc/expected.md b/doc/expected.md new file mode 100644 index 0000000..e1355f1 --- /dev/null +++ b/doc/expected.md @@ -0,0 +1,131 @@ +# expected + +## Synopsis + + // unexpected_ + + template using unexpected_ = variant; + + // bad_expected_access + + template class bad_expected_access; + + template<> class bad_expected_access: public std::exception + { + public: + + bad_expected_access() noexcept; + char const * what() const noexcept; + }; + + template class bad_expected_access: public bad_expected_access + { + public: + + explicit bad_expected_access( E const& e ); + E error() const; + }; + + // throw_on_unexpected + + template 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 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 + constexpr expected( unexpected_ const & x ); + + template + constexpr expected( unexpected_ && x ); + + // conversion constructor + + template + constexpr expected( expected const & x ); + + template + constexpr expected( expected && x ); + + // emplace + + template void emplace( A&&... a ); + template void emplace( std::initializer_list 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 constexpr bool has_error() const noexcept; + constexpr bool has_error() const noexcept; + + // error access + + unexpected_ unexpected() const; + + template constexpr E2 error() const noexcept; + constexpr mp_first error() const noexcept; + + // error mapping + + template /*see below*/ remap_errors( F && f ) const; + expected remap_errors(); + + // then + + template /*see below*/ operator>>( F && f ) const; + }; + + template inline constexpr bool operator==( expected const & x1, expected const & x2 ); + template inline constexpr bool operator!=( expected const & x1, expected const & x2 ); + + template inline void swap( expected & x1, expected & x2 ) noexcept( /*see below*/ ); + + } // namespace variant2 + } // namespace boost + + // is_expected + + template struct is_expected; + +## Reference + +... From be5f19f7cf2f03505c9344e9f7f77d2788aa7bfc Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 3 Jun 2017 14:45:22 +0300 Subject: [PATCH 04/11] Update expected.md --- doc/expected.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/expected.md b/doc/expected.md index e1355f1..b75022c 100644 --- a/doc/expected.md +++ b/doc/expected.md @@ -114,10 +114,14 @@ template /*see below*/ operator>>( F && f ) const; }; - template inline constexpr bool operator==( expected const & x1, expected const & x2 ); - template inline constexpr bool operator!=( expected const & x1, expected const & x2 ); + template + inline constexpr bool operator==( expected const & x1, expected const & x2 ); + + template + inline constexpr bool operator!=( expected const & x1, expected const & x2 ); - template inline void swap( expected & x1, expected & x2 ) noexcept( /*see below*/ ); + template + inline void swap( expected & x1, expected & x2 ) noexcept( /*see below*/ ); } // namespace variant2 } // namespace boost From 2b86468e67a89c1da9235b3d0964edfffed65d85 Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 3 Jun 2017 14:57:30 +0300 Subject: [PATCH 05/11] Update expected.md --- doc/expected.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/doc/expected.md b/doc/expected.md index b75022c..3e22e9e 100644 --- a/doc/expected.md +++ b/doc/expected.md @@ -29,8 +29,8 @@ // throw_on_unexpected template 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 ); + void throw_on_unexpected( std::error_code const& e ); + void throw_on_unexpected( std::exception_ptr const& e ); // expected @@ -43,23 +43,23 @@ constexpr expected() noexcept( /*see below*/ ); constexpr expected( T const& t ) noexcept( /*see below*/ ); - constexpr expected( T && t ) noexcept( /*see below*/ ); + constexpr expected( T&& t ) noexcept( /*see below*/ ); // unexpected constructor template - constexpr expected( unexpected_ const & x ); + constexpr expected( unexpected_ const& x ); template - constexpr expected( unexpected_ && x ); + constexpr expected( unexpected_&& x ); // conversion constructor template - constexpr expected( expected const & x ); + constexpr expected( expected const& x ); template - constexpr expected( expected && x ); + constexpr expected( expected&& x ); // emplace @@ -68,7 +68,7 @@ // swap - void swap( expected & r ) noexcept( /*see below*/ ); + void swap( expected& r ) noexcept( /*see below*/ ); // value queries @@ -102,34 +102,34 @@ unexpected_ unexpected() const; template constexpr E2 error() const noexcept; - constexpr mp_first error() const noexcept; + constexpr /*see below*/ error() const noexcept; // error mapping - template /*see below*/ remap_errors( F && f ) const; - expected remap_errors(); + template /*see below*/ remap_errors( F&& f ) const; + expected remap_errors() const; // then - template /*see below*/ operator>>( F && f ) const; + template /*see below*/ operator>>( F&& f ) const; }; template - inline constexpr bool operator==( expected const & x1, expected const & x2 ); + inline constexpr bool operator==( expected const& x1, expected const& x2 ); template - inline constexpr bool operator!=( expected const & x1, expected const & x2 ); + inline constexpr bool operator!=( expected const& x1, expected const& x2 ); template - inline void swap( expected & x1, expected & x2 ) noexcept( /*see below*/ ); - - } // namespace variant2 - } // namespace boost + inline void swap( expected& x1, expected& x2 ) noexcept( /*see below*/ ); // is_expected template struct is_expected; + } // namespace variant2 + } // namespace boost + ## Reference ... From 02ecef4fcbd2552474f93901fae81c5cecf1e06b Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 3 Jun 2017 14:58:44 +0300 Subject: [PATCH 06/11] Add const on remap_errors() --- include/boost/variant2/expected.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/variant2/expected.hpp b/include/boost/variant2/expected.hpp index 87022e8..d7339bc 100644 --- a/include/boost/variant2/expected.hpp +++ b/include/boost/variant2/expected.hpp @@ -375,7 +375,7 @@ public: }); } - expected remap_errors() + expected remap_errors() const { using R = expected; From d88ba98e92237bde7c04029ef77858c0031171b7 Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 3 Jun 2017 15:51:28 +0300 Subject: [PATCH 07/11] Throw bad_expected_access if throw_on_unexpected returns --- include/boost/variant2/expected.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/boost/variant2/expected.hpp b/include/boost/variant2/expected.hpp index d7339bc..539b70e 100644 --- a/include/boost/variant2/expected.hpp +++ b/include/boost/variant2/expected.hpp @@ -90,9 +90,8 @@ public: // throw_on_unexpected -template void throw_on_unexpected( E const& e ) +template void throw_on_unexpected( E const& /*e*/ ) { - throw bad_expected_access( e ); } void throw_on_unexpected( std::error_code const & e ) @@ -137,7 +136,10 @@ private: } else { - throw_on_unexpected( get(v_) ); + auto const & e = get(v_); + + throw_on_unexpected( e ); + throw bad_expected_access>( e ); } }); } From 6225ba33c33a06811537a4669e1fbe090a98390d Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 3 Jun 2017 15:52:07 +0300 Subject: [PATCH 08/11] Update doc/expected.md --- doc/expected.md | 168 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/doc/expected.md b/doc/expected.md index 3e22e9e..35bdaa2 100644 --- a/doc/expected.md +++ b/doc/expected.md @@ -1,5 +1,173 @@ # expected +## Description + +The class `expected` presented here is an extended version of `expected` 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](https://ned14.github.io/boost.outcome/), +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`. + +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, such as the two examples below: + + // Library 1 + + namespace lib1 + { + + enum class error + { + division_by_zero, + other_error + }; + + expected div( double x, double y ); + + } // namespace lib1 + + // Library 2 + + namespace lib2 + { + + enum class error + { + division_by_zero, + negative_logarithm + }; + + expected log( double x ); + + } // namespace lib2 + +In this proposal, combining the results of `lib1::div` and `lib2::log` can be achieved via +simple composition: + + expected 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 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(); + } + +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`, +as in D0323R2, but there are two differences. First, `bad_expected_access` objects +derive from a common base `bad_expected_access` 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` has been called `unexpected_` and is +an alias for `variant`. It is unfortunately not possible to use the name `unexpected`, +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` can be converted to `expected` 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 { + + 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>( 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_ From 2af60f13a2b81097679a14c08eb0fe6e24626aba Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 3 Jun 2017 15:58:27 +0300 Subject: [PATCH 09/11] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 182be6f..66c3ac4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # variant2 -This repository contains a never-valueless C++14 implementation of [std::variant](http://en.cppreference.com/w/cpp/utility/variant) in [variant.hpp](include/boost/variant2/variant.hpp) and an implementation of `expected` in [expected.hpp](include/boost/variant2/variant.hpp) that is an extended version of `extended` 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). +This repository contains a never-valueless C++14 implementation of [std::variant](http://en.cppreference.com/w/cpp/utility/variant) in [variant.hpp](include/boost/variant2/variant.hpp) and an implementation of `expected` in [expected.hpp](include/boost/variant2/expected.hpp) that is an extended version of `extended` 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 code requires [mp11](https://github.com/pdimov/mp11) and Boost.Config. The repository is intended to be placed into the `libs/variant2` directory of a Boost clone or release, with mp11 in `libs/mp11`, but the headers will also work standalone if [mp11.hpp](https://github.com/pdimov/mp11/blob/master/include/boost/mp11.hpp) or [mp11_single.hpp](https://github.com/pdimov/mp11/blob/master/include/boost/mp11_single.hpp) is included beforehand. @@ -27,4 +27,7 @@ If the second bullet doesn't hold, but the first does, the variant uses single s ## expected.hpp -The class `boost::variant2::expected` represents the return type of an operation that may potentially fail. It contains either the expected result of type `T`, or a reason for the failure of one of the error types in `E...`. Internally, this is represented as `variant`. +The class `boost::variant2::expected` represents the return type of an operation that may potentially fail. It contains either the expected result of type `T`, or a reason for the failure, of one of the error types in `E...`. Internally, this is stored as `variant`. + +See [its documentation](doc/expected.md) for more information. + From 1bd9c231ad4d9a0116962257cf390a9f4458e7ab Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 3 Jun 2017 16:13:04 +0300 Subject: [PATCH 10/11] Update expected.md --- doc/expected.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/expected.md b/doc/expected.md index 35bdaa2..8573f11 100644 --- a/doc/expected.md +++ b/doc/expected.md @@ -7,15 +7,13 @@ proposed in [P0323R1](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0 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](https://ned14.github.io/boost.outcome/), -can store either an error of type `std::error_code`, or an exception in the form of `std::exception_ptr`. +flexible. One example of a type of the `expected` family, [`outcome`](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`. 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, such as the two examples below: - - // Library 1 +each with its own error type. Library 1 may use `lib1::error`: namespace lib1 { @@ -30,7 +28,7 @@ each with its own error type, such as the two examples below: } // namespace lib1 - // Library 2 +while Library 2 might define its own `lib2::error`: namespace lib2 { @@ -85,6 +83,10 @@ An alternative approach that requires more effort is also supported: 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`, as in D0323R2, but there are two differences. First, `bad_expected_access` objects From e68ff1f014a56453382a7c1bac7e09501e4f67ab Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sun, 4 Jun 2017 13:55:42 +0300 Subject: [PATCH 11/11] Fix copy/paste mistake --- include/boost/variant2/expected.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/variant2/expected.hpp b/include/boost/variant2/expected.hpp index 539b70e..24a46fd 100644 --- a/include/boost/variant2/expected.hpp +++ b/include/boost/variant2/expected.hpp @@ -173,7 +173,7 @@ public: } template..., mp_contains, E2>...>, void>> + class En = mp_if..., mp_contains, E2>...>, void>> constexpr expected( unexpected_ && x ): v_( std::move(x) ) { }