Boost.Outcome review - First questions

Hi, I've tried to read the tutorial, but it is too long for my taste. I will try it later on :( A minor issue * "As inferred in the example code cases earlier, you can check if an |expected<T, E>| has an expected value via a simple test for boolean true or more explicitly via |.has_value()| before retrieving the value using |.value()|." This is not correct. It is correct for the de-reference operator, but .value() don't needs any check with .has_value(). Instead of some deviation, I would expect a report of all deviations and a rationale explaining why it is better to do this way. # "Types |T| and |E| cannot be constructible into one another. This is a fundamental design choice in basic_monad to significantly reduce compile times so it won't be fixed." * "Note though that |expected<void, void>| is permitted (which is legal in the LEWG proposal)." What are the compile time cost |associated the std::experimental::expected<T,E>? | I don't see what |expected<void, void> could mean. | # |unexpected_type<E>| is implemented as an |expected<void, E>| and it lets the basic_monad machinery do the implicit conversion to some |expected<T, E>| through the less representative to more representative conversion rules. Note that our Expected passes the LEWG Expected test suite just fine, so you probably won't notice this implementation detail in your code. The test of my POC implementation couldn't be complete, so this is not a sign of compatibility. Which operation allow to convert expected<void,E> to expected<T,E> and what is the expected behavior if expected<void,E> has a value? # Our |expected<T, E>| defaults E to |std::error_code| rather than |std::error_condition| (the LEWG proposal fixes this in P0323R2). I don't have a strong opinion on which default would be the best. As you suggested, I have added it to the next revision. We don't implement the ordering and hashing operator overloads due to https://akrzemi1.wordpress.com/2014/12/02/a-gotcha-with-optional/. The fact the LEWG proposal does as currently proposed is a defect (it will be discussed by LEWG with P0323R2 in Toronto). As for optional this could be considered a defect. The question is if we want expected to differen from the optional interface. If we considered it a defect, I don't believe that we should remove comparison, but IMHO we should make the constructor from T explicit. I don't expect the committee to fix this for expected and let the issue for optional. We will see. # We don't implement |make_expected_from_call()| as we think it highly likely to be removed from the next version of the proposal due to it conferring little value. This is possible and the function can be implemented very easily. I can live with and without it. # Our |make_expected()| and |make_unexpected()| functions deviate significantly as we believe the LEWG ones to be defective in various ways. This topic will be discussed in Toronto and reconciliation later is highly likely. Please, could you tell how those are defective so that we can fix them? Otherwise I don't see what we could discuss in Toronto. # Our |value_or()| member functions pass through the reference rather than returning by 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). Well, what is the advantage to returning by reference? 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 |expected<T, E>::has_value_type| and |expected<T, E>::has_error_type| 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. Sorry. I don't understand what is the issue. Could you elaborate? # 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: Defining these operation when they can not be implemented means that you cannot use SFINAE on this type. Maybe I'm wrong, but I believe we want to check if a type is constructible, ... using the C++ standard type traits. Next follow the first questions I have mainly from the reference documentation of expected<T,E>. In general I find most of the operation not specified enough. I would like to have something in line with the standard way of specifying an interface. * it is not clear from the reference documentation if expected<void,E> is valid? |* |is expected<T,E> the sum type of T, E and empty? and exception_ptr? |"outcome<T>| can be empty, or hold an instance of a type |T|, or hold an instance of |error_code_extended|, or hold an instance of |std::exception_ptr|. This saves you having to wrap an error code into an exception pointer instance, thus also avoiding a memory allocation." std::experimental::expected requires that its Error type to be no throw constructible, copyable and movable. What are the exception warranties for |error_code_extended? | What are the exception warranties for |outcome<T>||? | * inplace construction What is the difference between: constexpr expected (value_t _) noexcept(std::is_nothrow_default_constructible< value_type >::value) Implicit constructor of a valued monad (default constructed) and constexpr expected (in_place_t, Args &&... args) noexcept(std::is_nothrow_constructible< value_type, Arg, Args... >::value) when there is no Args. IMO the second subsumes the first and so there is no need fro the first overload and the value_t tag. * default constructor constexpr expected () noexcept(is_nothrow_default_constructible) Default constructor. This doesn't say what the default constructor does. * Shouldn't constexpr expected (in_place_t, std::initializer_list< U > l) noexcept(std::is_nothrow_constructible< value_type, std::initializer_list< U >>::value) Implicit constructor from an initializer list. be constexpr explicit expected(in_place_t, initializer_list<U> il, Args&&... args); * What is void_rebound and where it is defined constexpr expected (const void_rebound &v) noexcept(std::is_nothrow_copy_constructible< error_type >::value) Implicit constructor from an identically configured basic_monad<void> by copy. * construction from error type What is wrong with template <class... Args> constexpr explicit expected(unexpect_t, Args&&...); template <class U, class... Args> constexpr explicit expected(unexpect_t, initializer_list<U>, Args&&...); Why do you prefer an implicit conversion. This deviation must be in the rationale of the differences. constexpr expected (const error_type &v) noexcept(std::is_nothrow_copy_constructible< error_type >::value) Implicit constructor from a error_type by copy. constexpr expected (error_type &&v) noexcept(std::is_nothrow_move_constructible< error_type >::value) * raw types It is not clear what is the difference between xxx and raw_xxx typedef value_storage_type::value_type value_type The type potentially held by the monad. typedef implementation_policy::value_type raw_value_type The raw type potentially held by the monad. typedef value_storage_type::error_type error_type The error code potentially held by the monad. typedef implementation_policy::error_type raw_error_type The raw error code potentially held by the monad. typedef value_storage_type::exception_type exception_type The exception ptr potentially held by the monad. typedef implementation_policy::exception_type raw_exception_type The raw exception ptr potentially held by the monad. * What is the "The exception ptr potentially held by the monad"? * what is the sizeof expected <T,E>? * is basic_monad something proposed or an implementation detail? If the first, this should be renamed, if the second this class should not appear in the documentation and the word monad should be banned. * What is expected<Policy> in template<class Policy , typename = typename std::enable_if<std::is_same<typename implementation_policy::value_type, typename Policy::value_type>::value || std::is_void<typename Policy::value_type>::value || std::is_constructible<typename implementation_policy::value_type, typename Policy::value_type>::value>::type, typename = typename std::enable_if<std::is_same<typename implementation_policy::error_type, typename Policy::error_type>::value || std::is_constructible<typename implementation_policy::error_type, typename Policy::error_type>::value>::type, typename = typename std::enable_if<std::is_same<typename implementation_policy::exception_type, typename Policy::exception_type>::value || std::is_constructible<typename implementation_policy::exception_type, typename Policy::exception_type>::value>::type> constexpr expected (expected< Policy > &&o, explicit_conversion_from_different_policy=explicit_conversion_from_different_policy()) Explicit move constructor from a basic_monad with a differing implementation policy. For this constructor to be available, value_type, error_type and exception_type must be identical or constructible. * move constructor/copy constructor The reference documentation doesn't says what this does. * Missing move/copy assignment * bool conversion should be explicit * is_ready has nothing to be with expected. * Shouldn't value_or return by value? otherwise what would be the difference between get_or and value_or? * emplace. Missing overload template <class U, class... Args> void emplace(initializer_list<U>, Args&&...); * swap: What is the meaning of swapping an expected with an option? swap must follow the standard signature. * Missing preconditions on the observers, which make them wide operations. Saying that they return an address makes them wide operations however accessing to the contents of this address will be undefined behavior. I prefer the standard way, defining a narrow function and stating clearly the pre-conditions. * what error_or <https://ned14.github.io/boost.outcome/structboost_1_1outcome_1_1v1__xxx_1_1policy_1_1expected__policy__base.html#ae0882c7d380b88b9011bcc60c59903bd> returns if it contains an exception? * what is the difference between get_error_or and error_or? https://github.com/ned14/boost.outcome/issues/19 * Missing overload for get_unexpected. In addition it doesn't return an unexpected_type. constexpr const E & get_unexpected () const; Best, Vicente
participants (1)
-
Vicente J. Botet Escriba