Skip to content

Commit

Permalink
Merged from develop branch as CDash reports all green
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins nedprod CI committed May 11, 2017
2 parents 56c6ea3 + 4d9e404 commit 98f8912
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 60 deletions.
4 changes: 2 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Past tarballs of source with all tests passing on Linux and Windows: https://ded

Before Boost peer review:
- [ ] Write up errata for ACCU talk for under the video.
- [x] Fix operator*(), operator->() and .error() in Expected.
- [x] error_or() ought to have rvalue ref etc overloads, and exception_or()
- [x] Fix operator*(), operator->() and .error() in Expected [issus #10].
- [x] error_or() ought to have rvalue ref etc overloads, and exception_or() [issue #10].

Later after Boost peer review:
- [ ] Create new Win32 and NT error code categories and have make_errored_outcome() use those.
Expand Down
2 changes: 1 addition & 1 deletion doc/html
Submodule html updated from 36e289 to 2fb650
2 changes: 1 addition & 1 deletion doc/md/00-mainpage.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ affect some applications. In this situation, use LLVM clang targeting the MSVC A

\page support Support

<a href="https://stackoverflow.com/">StackOverflow</a> with the boost-outcome tag is the
<a href="https://stackoverflow.com/">StackOverflow</a> with the <tt>boost-outcome</tt> tag is the
preferred place to ask questions on usage.

If you have a problem, please review the FAQ and the wiki. Searching
Expand Down
2 changes: 1 addition & 1 deletion doc/md/01-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ OS X with Xcode and on Windows with VS2015.
A nightly cronjob figures out for which latest commit all tests passed on all those platforms and
merges that commit to master branch. From that master branch, a source tarball is generated.

Under a default configuration, when you `#include "boost.outcome/include/boost/outcome.hpp"`
Under a default configuration, when you <tt>\#include "boost.outcome/include/boost/outcome.hpp"</tt>
you will include a preprocessed single file edition for maximum build performance in
downstream projects. This can interfere with accurate bug reporting in Outcome, so if you
are diagnosing a problem in Outcome itself, predefine
Expand Down
18 changes: 15 additions & 3 deletions doc/md/02-tutorialA.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,18 @@ valueless due to exception state ever appears, we throw a `bad_expected_access<v
exception when you try to access an Expected which is valueless, as an `expected<T, E = void>`
is not a possible Expected configuration due to requiring E to be constructible.

\note By default Outcome static asserts if you use any its transports with a type or types
which have throwing move constructors, warning you that in that use case you must write code
to cope with valueless due to exception. These static asserts can be disabled using a type trait
on a type by type basis, but this default behaviour will prevent accidental surprises when using
Outcome's non-conforming Expected implementation.

\warning These sorts of monadic transport are particularly sensitive to generating code and memory
bloat if their move constructor can throw due to all the extra work Outcome must do to ensure
exception safety. Try, whenever possible, to only use types in these transports with
non-throwing move semantics (i.e. don't quickly disable the static assert above unless there
is no other option available).

\subsubsection expected_observers Expected's observers

Generally speaking you will not want Expected to throw `bad_expected_access<E>` as a
Expand All @@ -566,10 +578,10 @@ public:
using type = expected<U, error_type>;
};
// As if &value()
// As if reinterpret_cast<value_type *>, no runtime checking performed!
constexpr const T* operator ->() const;
T* operator ->();
// As if value()
// As if reinterpret_cast<value_type>, no runtime checking performed!
constexpr const T& operator *() const&;
T& operator *() &;
constexpr const T&& operator *() const &&;
Expand All @@ -585,7 +597,7 @@ public:
constexpr const T&& value() const &&;
constexpr T&& value() &&;
// Returns a lvalue or rvalue ref to the stored error_type if there is one, else undefined behaviour
// As if reinterpret_cast<error_type>, no runtime checking performed!
constexpr const E& error() const&;
E& error() &;
constexpr E&& error() &&;
Expand Down
4 changes: 2 additions & 2 deletions doc/md/03-tutorialB.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ maintenance burden over time is obvious, and a further worked example follows be
The third reason is that the C++ 11 standard library already provides an enum of the
most common error codes for you so you don't feel like going off and reinventing
the wheel. It's called <a href="http://en.cppreference.com/w/cpp/error/errc">`std::errc`</a>
brought in by `#include <system_error>` and as you'll see, it contains the standard
brought in by <tt>\#include <system_error></tt> and as you'll see, it contains the standard
POSIX error codes, and most of the time you'll find that whatever custom error code
domain you are about to write can be adequately covered by `std::errc`.
In fact, let's try it:
Expand Down Expand Up @@ -202,7 +202,7 @@ abstraction layer where errors from the OS ought to be returned exactly as from
you should leave `E` default to `std::error_code` and return `std::system_category()` error
codes from `errno` or `GetLastError()` as appropriate. If you don't care and/or you're in a rush,
`E = std::error_code` and returning some enumeration from `std::errc` will get you working code
the quickest (and be aware that Outcome's refinements of `expected<T, E>` `outcome<T>` and `result<T>`
the quickest (and be aware that Outcome's refinements of `expected<T, E>`, `outcome<T>` and `result<T>`
hard code the `E` to an enhanced `error_code_extended` and will save you typing a fair bit of boilerplate).
As the latter two use patterns may be unclear, a worked example follows in the next section.

Expand Down
107 changes: 90 additions & 17 deletions doc/md/04-tutorialC.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ instead there is a formal empty state to do with as you please (in some situatio
returning a value OR empty OR an error makes a lot of sense and Outcome supports a `tribool`
extension for ternary logics for those who need such a thing). Attempting to retrieve a value,
error or exception from an empty transport throws a `monad_error::no_state` [1].
- Implied in this design is that unlike in Expected, your type `T` need not be default
constructible, nor movable, nor copyable. If your type `T` throws during a switch between
states, we adopt the formal empty state.
- `.value()` on an errored transport directly throws a `std::system_error` with the error code [2]
instead of throwing a wrapper type like `bad_expected_access<E>` from which you need to manually
extract the `E` instance later.
Expand All @@ -217,15 +220,29 @@ extract later.
- `.error()` returns any `error_code_extended` as you'd expect, returning a default constructed
(null) error code if the transport is valued, or `monad_errc::exception_present` if the
transport is excepted.
- Unlike Expected, this is always a by-value return instead of by-reference via
`reinterpret_cast<error_type>`. This is low cost, because we know what our error type always is.
- `.exception()` returns any `std::exception_ptr` as you'd expect. If the transport is valued,
it returns a default constructed (null) exception pointer. If the transport is errored, it
returns an exception pointer to a `std::system_error` wrapping the error code.

And finally, if your compiler is in C++ 17 mode or later, transports are marked with the
- `operator->()` and `operator*()` both alias `.value()` i.e. they do not implicitly do
`reinterpret_cast<value_type>` like Expected does. In fact, Outcome's refinements **never ever**
implicitly call `reinterpret_cast<>`, and you can make use of this to save writing considerable
boilerplate by letting these throwing semantics just described activate as needed instead of
you having to manually check state before access as you must do with Expected.
- Due to historically also providing a monadic future-promise implementation, we also provide `.get()`,
`.get_error()`, `.get_exception()` and so on. These are aliases to `.value()`, `.error()` and
`.exception()`. We have found the aliases useful in our own code where we use the convention of
`.value()` when we make use of the value returned, and `.get()` to indicate we are purely
retrieving the value to cause the rethrow of any errors or exceptions. We have found this makes
code clearer. You may find using the same convention useful in your own code too.

Additionally, if your compiler is in C++ 17 mode or later, transports are marked with the
<a href="http://en.cppreference.com/w/cpp/language/attributes">`[[nodiscard]]`</a> attribute.
This means the compiler ought to warn if you forget to examine transports returned by functions.
If you really mean to throw away a returned transport, make sure you cast it to `(void)` to tell
the compiler you specifically intend to throw it away.
the compiler you specifically intend to throw it away. This solves a big pain point when using
`error_code &ec` pass back overloads from C++ 11 where you forget to check `ec`.

[1]: Actually the `BOOST_OUTCOME_THROW_MONAD_ERROR(ec, monad_error(monad_errc::no_state))` macro is executed
where `ec` is the `std::error_code` contained by the monad. This can be user redefined to do anything,
Expand Down Expand Up @@ -265,12 +282,12 @@ result<Foo> somefunction()

The full set of free functions is as follows:
<dl>
<dt>`make_[option|result|outcome](T v)`</dt>
<dd>Makes a valued transport with value `v`.</dd>
<dt>`make_[option|result|outcome]<T = void>()`</dt>
<dd>Makes a valued transport default constructing a `T`.</dd>
<dt>`make_empty_[option|result|outcome]<T = void>()`</dt>
<dd>Makes an empty transport.</dd>
<dt>`make_valued_[option|result|outcome](T v)`</dt>
<dd>Makes a valued transport with value `v`.</dd>
<dt>`make_valued_[option|result|outcome]<T = void>()`</dt>
<dd>Makes a valued transport default constructing a `T`.</dd>
<dt>`make_errored_[result|outcome]<T = void>(error_code_extended ec)`</dt>
<dd>Makes an errored transport.</dd>
<dt>`make_errored_[result|outcome]<T = void>(int code, const char *extendedmsg = nullptr)`</dt>
Expand All @@ -281,7 +298,7 @@ very convenient for writing:
result<Foo> somefunction()
{
if(-1 == open(...)) // Any POSIX system call setting errno on failure
return make_errored_result<>(errno);
return make_errored_result(errno);
}
~~~
</dd>
Expand All @@ -294,7 +311,7 @@ very convenient for writing:
result<Foo> somefunction()
{
if(INVALID_HANDLE_VALUE == CreateFile(...)) // Any Win32 call setting GetLastError() on failure
return make_errored_result<>(GetLastError());
return make_errored_result(GetLastError());
}
~~~
</dd>
Expand All @@ -309,7 +326,7 @@ very convenient for writing:
}
catch(...) // catch all
{
return make_exceptional_outcome</*void*/>(/* std::current_exception() */);
return make_exceptional_outcome/*<void>*/(/* std::current_exception() */);
}
}
~~~
Expand All @@ -322,11 +339,54 @@ as a void version of the same transport, preserving any empty/errored/excepted s
was valued, the returned copy will be valued **void**.</dd>
</dl>

Outcome's refinements do not permit `T` to be constructible from an `error_code_extended`
or `std::exception_ptr` and vice versa for much improved compile times. This allows us
to offer implicit construction from all three types, so you can entirely avoid any of the
`make_xxx()` functions just described and just return the state you desire directly.

We have however been using these in our own code for two years now, and we recommend that
you always use the `make_xxx()` functions in code intended for a long life span
because they make code correctness auditing so much easier - with a single glance you can
tell a function's error handling is correct or not. If the code
is written to rely on the implicit construction, any function of any complexity requires in
depth study to determine correctness.

All that just said, in a small function, particularly in boilerplate functions, it's
enormously less cluttered to not have to write `make_xxx()` all the time. So choose which
form you use carefully on a function by function basis.

Finally, it may not be obvious that the above syntax also allows contract programming e.g. this is a style used throughout
AFIO v2 which proven to be very useful for self-documenting the function's contract with
the caller:

~~~{.cpp}
result<file_handle::extent_type> file_handle::truncate(file_handle::extent_type newsize) noexcept
{
BOOST_AFIO_LOG_FUNCTION_CALL(_v.fd);
if(ftruncate(_v.fd, newsize) < 0)
return make_errored_result<extent_type>(errno, last190(_path.native()));
if(are_safety_fsyncs_issued())
::fsync(_v.fd); // deliberately ignore failure
return make_valued_result<extent_type>(newsize);
}
~~~

The paths taken for success and failure are extremely obvious and jump immediately to
the eye. We manually specify the exact result type returned rather than letting Outcome
perform automatic upconversion, partially for improved compile times as we avoid the
upconversion metaprogramming, but also because in a longer function the exact type
being returned is usually off the top of the screen, and manually specifying it is
useful documentation. Obviously each code base will be different, but consider using the
style above if code correctness and fast auditing is particularly important to you for
a project you expect to be maintaining for many years.


<hr><br>

\section outcome_macros Outcome's helper macros

We've already seen in part B the helper macro `BOOST_OUTCOME_TRY(var, expr)` which also works
We have already seen in the previous part of this tutorial that the helper macro
`BOOST_OUTCOME_TRY(var, expr)` which also works
with `expected<T, E>` when the error type in the returned Expected is the same. Something
unique to Outcome's refinements due to using `error_code_extended` is the convenience macro
`BOOST_OUTCOME_CATCH_EXCEPTION_TO_RESULT` which is a long
Expand Down Expand Up @@ -381,9 +441,12 @@ catch(const std::exception &e)
~~~

This macro is particularly useful when you are calling into the STL from a noexcept
function which returns a `result<T>` and not an `outcome<T>`. So long as the code you
function (e.g. "island of exception throw in a sea of noexcept") which returns a
`result<T>` and not an `outcome<T>`. So long as the code you
call only **ever** will throw STL exception types, using this macro is safe and information
loss free. Note that the above sequence does not include a catch all clause, so if
loss free, plus some compiler optimisers will elide the costly exception throw and catch
machinery and replace it with a simple return of the result, thus effectively doing "de-exception-throw"
to the C++ STL which is very, very useful. Note that the above sequence does not include a catch all clause, so if
you want one of those then use the `BOOST_OUTCOME_CATCH_ALL_EXCEPTION_TO_RESULT` macro
which adds to the end of the catch sequence:

Expand Down Expand Up @@ -456,10 +519,10 @@ public API with the signature:
outcome::expected<void, outcome::expected<std::error_code, std::exception_ptr>> execute(std::function<io_handler> f) noexcept;
~~~
This is a nested Expected used to transport one of two unexpected values. In real
world usage of Expected you'll find yourself writing this more than you'd prefer
world usage of Expected you'll find yourself writing this more than you'd prefer,
and indeed a historical edition of the Expected proposal had special convenience
semantics for them. The current proposal does not however, so you end up writing
clunky code.
clunky code, which isn't necessary with Outcome's refinements.

Indeed, it would be worse again if it weren't for the `std::future` being used
to carry any exception throws for us which lets us skip a nested Expected. One
Expand All @@ -468,7 +531,17 @@ simply rethrow any transported exceptions instead of messing around with nested
Expected, however the "sea of noexcept" public API design would then be lost and the
two examples no longer equivalent.

To conclude, this aspect of simplifying the programmer's effort to write
Another thing which may or may not seem neat to you is that in the Outcome edition,
`outcome<T>` is errored not excepted when it was a known C++ exception type, and
only excepted if it was an unknown C++ exception throw. This is a very powerful
segmentation in practice because it lets you separate the truly unexpected and
unknown failures from the less serious ones. One can therefore employ an abort
on the truly unknown failure strategy quite like Rust does with its `panic!`
approach, yet retain normal C++ exception throws and unwinds where the failure
is not as unknown. Lots of design flexibility and potential lies here, it all
depends on your design and your code base.

To conclude, this aspect of simplifying the programmer's effort to write various
"sea of noexcept" API designs is precisely what Outcome's refinements are
intended for. If Outcome's refinements suit well your code base, they should
let you write clearer, more maintainable and less buggy code. *Buen provecho!*
let you write clearer, less verbose, more maintainable and less buggy code. *Buen provecho!*
19 changes: 12 additions & 7 deletions doc/md/08-expectedsynopsis.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ As you will note, there are technically speaking quite a lot of deviations. Most
and will present no effect in code using Expected. Occasionally a function is missing or implemented
significantly differently. If it has a significantly different signature, it is usually because we
feel the LEWG Expected is defective in that area (e.g. the use of `decay<>` for the factory functions
precludes manufacturing `expected<const T>` which we have found to be in practice very useful). If
it is missing, it is either because we feel its presence is a defect in the LEWG Expected or we think
it not worth implementing.
precludes manufacturing `expected<const T>` which we have found to be in practice very useful).

Some of Outcome's Expected deviations from P0323R1 have entered P0323R2 which will be presented at
the Toronto WG21 meeting in July 2017. Once P0323R2 has been published, Outcome's Expected
Expand Down Expand Up @@ -63,16 +61,23 @@ removed from the next version of the proposal due to it conferring little value.
we believe the LEWG ones to be defective in various ways. This topic will be discussed
in Toronto and reconciliation later is highly likely.
- Our `value_or()` member functions pass through the reference rather than returning by
value (which we don't understand why the LEWG proposal does).
value (we don't understand why the LEWG proposal does except that `std::optional<T>`
does, which itself is a bad design choice and it should be changed).
- Rather than `void`, our `value_type` and `error_type` typedefs are set to a descriptive
compiler error generating type if the Expected is configured with `void`. If you want
to know if no type was configured, use the static constexpr bools at
<tt>expected<T, E>::has_value_type</tt> and <tt>expected<T, E>::has_error_type</tt>
or use `raw_value_type` and `raw_error_type`. The reason for this deviation is to
make metaprogramming using Outcome's transports much easier and less error prone.
- Our Expected always defines the default, copy and move constructors even if the
the type configured is not capable of it. That means `std::is_copy_constructible`
etc returns true when they should return false. The reason why is again to
significantly improve compile times by hugely reducing the number of templates
which need to be instantiated during routine basic_monad usage, and again
this won't be fixed. Instead use the static constexpr bools at:
- <pre>expected<T, E>::is_default_constructible</pre>
- <pre>expected<T, E>::is_copy_constructible</pre>
- <pre>expected<T, E>::is_move_constructible</pre>
- <tt>expected<T, E>::is_default_constructible</tt>
- <tt>expected<T, E>::is_copy_constructible</tt>
- <tt>expected<T, E>::is_move_constructible</tt>
\note Depending on what any Boost peer review thinks, we may inject correct answers
for the type traits for basic_monad into namespace std.

Expand Down
2 changes: 1 addition & 1 deletion example/expected_try1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ using MathResult2 = outcome::expected<long long, MathError2>;

MathResult1 div(double x, double y) noexcept
{
if (::fabs(y) < FLT_EPSILON)
if (::fabs(y) < DBL_EPSILON)
{
// This operation would fail, instead let's return the reason of
// the failure wrapped in E
Expand Down
Loading

0 comments on commit 98f8912

Please sign in to comment.