3973. Monadic operations should be ADL-proof

Section: 22.8.6.7 [expected.object.monadic], 22.5.3.8 [optional.monadic] Status: WP Submitter: Jiang An Opened: 2023-08-10 Last modified: 2023-11-22

Priority: Not Prioritized

View all other issues in [expected.object.monadic].

View all issues with WP status.

Discussion:

LWG 3938 switched to use **this to access the value stored in std::expected. However, as shown in LWG 3969, **this can trigger ADL and find an unwanted overload, and thus may caused unintended behavior.

Current implementations behave correctly (Godbolt link): they don't direct use **this, but use the name of the union member instead.

Moreover, P2407R5 will change the monadic operations of std::optional to use **this, which is also problematic.

[2023-09-19; Wording update]

Several people preferred to replace operator*() by the corresponding union members, so this part of the proposed wording has been adjusted, which is a rather mechanical replacement.

[2023-10-30; Reflector poll]

Set status to Tentatively Ready after five votes in favour during reflector poll.

[2023-11-11 Approved at November 2023 meeting in Kona. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4958.

  1. Modify 22.8.6.7 [expected.object.monadic] as indicated:

    [Drafting note: Effectively replace all occurrences of **this by val, except for decltype.]

    template<class F> constexpr auto and_then(F&& f) &;
    template<class F> constexpr auto and_then(F&& f) const &;
    

    -1- Let U be remove_cvref_t<invoke_result_t<F, decltype(**this(val))>>.

    -2- […]

    -3- […]

    -4- Effects: Equivalent to:

    if (has_value())
      return invoke(std::forward<F>(f), **thisval);
    else
      return U(unexpect, error());
    
    template<class F> constexpr auto and_then(F&& f) &&;
    template<class F> constexpr auto and_then(F&& f) const &&;
    

    -5- Let U be remove_cvref_t<invoke_result_t<F, decltype((std::move(**thisval))>>.

    -6- […]

    -7- […]

    -8- Effects: Equivalent to:

    if (has_value())
      return invoke(std::forward<F>(f), std::move(**thisval));
    else
      return U(unexpect, std::move(error()));
    
    template<class F> constexpr auto or_else(F&& f) &;
    template<class F> constexpr auto or_else(F&& f) const &;
    

    -9- Let G be remove_cvref_t<invoke_result_t<F, decltype(error())>>.

    -10- Constraints: is_constructible_v<T, decltype(**this(val))> is true.

    -11- […]

    -12- Effects: Equivalent to:

    if (has_value())
      return G(in_place, **thisval);
    else
      return invoke(std::forward<F>(f), error());
    
    template<class F> constexpr auto or_else(F&& f) &&;
    template<class F> constexpr auto or_else(F&& f) const &&;
    

    -13- Let G be remove_cvref_t<invoke_result_t<F, decltype(std::move(error()))>>.

    -14- Constraints: is_constructible_v<T, decltype(std::move(**thisval))> is true.

    -15- […]

    -16- Effects: Equivalent to:

    if (has_value())
      return G(in_place, std::move(**thisval));
    else
      return invoke(std::forward<F>(f), std::move(error()));
    
    template<class F> constexpr auto transform(F&& f) &;
    template<class F> constexpr auto transform(F&& f) const &;
    

    -17- Let U be remove_cvref_t<invoke_result_t<F, decltype(**this(val))>>.

    -18- […]

    -19- Mandates: U is a valid value type for expected. If is_void_v<U> is false, the declaration

    U u(invoke(std::forward<F>(f), **thisval));
    

    is well-formed.

    -20- Effects:

    1. (20.1) — […]

    2. (20.2) — Otherwise, if is_void_v<U> is false, returns an expected<U, E> object whose has_val member is true and val member is direct-non-list-initialized with invoke(std::forward<F>(f), **thisval).

    3. (20.3) — Otherwise, evaluates invoke(std::forward<F>(f), **thisval) and then returns expected<U, E>().

    template<class F> constexpr auto transform(F&& f) &&;
    template<class F> constexpr auto transform(F&& f) const &&;
    

    -21- Let U be remove_cvref_t<invoke_result_t<F, decltype(std::move(**thisval))>>.

    -22- […]

    -23- Mandates: U is a valid value type for expected. If is_void_v<U> is false, the declaration

    U u(invoke(std::forward<F>(f), std::move(**thisval)));
    

    is well-formed.

    -24- Effects:

    1. (24.1) — […]

    2. (24.2) — Otherwise, if is_void_v<U> is false, returns an expected<U, E> object whose has_val member is true and val member is direct-non-list-initialized with invoke(std::forward<F>(f), std::move(**thisval)).

    3. (24.3) — Otherwise, evaluates invoke(std::forward<F>(f), std::move(**thisval)) and then returns expected<U, E>().

    template<class F> constexpr auto transform_error(F&& f) &;
    template<class F> constexpr auto transform_error(F&& f) const &;
    

    -25- Let G be remove_cvref_t<invoke_result_t<F, decltype(error())>>.

    -26- Constraints: is_constructible_v<T, decltype(**this(val))> is true.

    -27- Mandates: […]

    -28- Returns: If has_value() is true, expected<T, G>(in_place, **thisval); otherwise, an expected<T, G> object whose has_val member is false and unex member is direct-non-list-initialized with invoke(std::forward<F>(f), error()).

    template<class F> constexpr auto transform_error(F&& f) &&;
    template<class F> constexpr auto transform_error(F&& f) const &&;
    

    -29- Let G be remove_cvref_t<invoke_result_t<F, decltype(std::move(error()))>>.

    -30- Constraints: is_constructible_v<T, decltype(std::move(**thisval))> is true.

    -31- Mandates: […]

    -32- Returns: If has_value() is true, expected<T, G>(in_place, std::move(**thisval)); otherwise, an expected<T, G> object whose has_val member is false and unex member is direct-non-list-initialized with invoke(std::forward<F>(f), std::move(error())).

  2. Modify 22.5.3.8 [optional.monadic] as indicated:

    [Drafting note: Effectively replace all occurrences of value() by *val.]

    template<class F> constexpr auto and_then(F&& f) &;
    template<class F> constexpr auto and_then(F&& f) const &;
    

    -1- Let U be invoke_result_t<F, decltype(value()*val)>.

    -2- […]

    -3- Effects: Equivalent to:

    if (*this) {
      return invoke(std::forward<F>(f), value()*val);
    } else {
      return remove_cvref_t<U>();
    }
    
    template<class F> constexpr auto and_then(F&& f) &&;
    template<class F> constexpr auto and_then(F&& f) const &&;
    

    -4- Let U be invoke_result_t<F, decltype(std::move(value()*val))>.

    -5- […]

    -6- Effects: Equivalent to:

    if (*this) {
      return invoke(std::forward<F>(f), std::move(value()*val));
    } else {
      return remove_cvref_t<U>();
    }
    
    template<class F> constexpr auto transform(F&& f) &;
    template<class F> constexpr auto transform(F&& f) const &;
    

    -7- Let U be remove_cv_t<invoke_result_t<F, decltype(value()*val)>>.

    -8- Mandates: U is a non-array object type other than in_place_t or nullopt_t. The declaration

    U u(invoke(std::forward<F>(f), value()*val));
    

    is well-formed for some invented variable u.

    […]

    -9- Returns: If *this contains a value, an optional<U> object whose contained value is direct-non-list-initialized with invoke(std::forward<F>(f), value()*val); otherwise, optional<U>().

    template<class F> constexpr auto transform(F&& f) &&;
    template<class F> constexpr auto transform(F&& f) const &&;
    

    -10- Let U be remove_cv_t<invoke_result_t<F, decltype(std::move(value()*val))>>.

    -11- Mandates: U is a non-array object type other than in_place_t or nullopt_t. The declaration

    U u(invoke(std::forward<F>(f), std::move(value()*val)));
    

    is well-formed for some invented variable u.

    […]

    -12- Returns: If *this contains a value, an optional<U> object whose contained value is direct-non-list-initialized with invoke(std::forward<F>(f), std::move(value()*val)); otherwise, optional<U>().