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."
  • 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 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