Boost logo

Boost :

Subject: Re: [boost] [outcome] To variant, or not to variant?
From: Peter Dimov (lists_at_[hidden])
Date: 2017-05-31 14:26:22


Gavin Lambert wrote:
> I probably should have thought of asking this earlier, but it occurs to me
> now that my own mental model of how an "outcome"-ish type should act is
> probably not suited to variant storage at all.
>
> So just out of curiosity I thought I'd ask whether people prefer this sort
> of interface:
...
> Or this sort of interface:
...

Neither. I prefer a combination of the two. Like a variant, exactly one of
has_value(), has_error(), has_exception() should report true, depending on
whether you called set_value, set_error, or set_exception. The accessors
however, should work as you previously outlined.

    // throws when !has_value()
    T value() const;

    // error_code() when has_value()
    // error when has_error()
    // errc::has_exception when has_exception()
    error_code error() const noexcept;

    // nullptr when has_value()
    // either nullptr or make_exception_ptr(system_error()) when has_error()
    // exception when has_exception()
    exception_ptr exception() const noexcept;

Proposed additional narrow:

    // nullptr when !has_value(), otherwise &value_
    T* operator->() noexcept;
    T const* operator->() const noexcept;

    // *operator->()
    T& operator() & noexcept;
    T const& operator() const & noexcept;
    T&& operator() && noexcept;
    T const&& operator() const && noexcept;

value() can also have the four-overload form, not shown for brevity.

Is there anyone that objects to that model?

I am sympathetic to people's desire to have a statically checkable value
accessor, but I see no reason to provide such for error() or exception().

The "exactly one is true" requirement allows all possible checks to be
expressed in a concise manner. If you want to test for error or exception,
say, that's !has_value.

> template<typename T>
> class outcome
> {
> public:
> bool has_value() const { return m_storage.has<T>(); }
> T& value() { return m_storage.get<T>(); }
>
> bool has_error() const { return m_storage.has<error_code>(); }
> error_code& error() { return m_storage.get<error_code>(); }
>
> bool has_exception() const { return m_storage.has<exception_ptr>(); }
> exception_ptr& exception() { return m_storage.get<exception_ptr>(); }
>
> void set(none_t) { m_storage = none; }
> void set(const T& val) { m_storage = val; }
> void set(error_code err) { m_storage = err; }
> void set(exception_ptr ep) { m_storage = ep; }
>
> private:
> variant<none_t, T, error_code, exception_ptr> m_storage;
> };
>
> Or this sort of interface:
>
> template<typename T>
> class outcome
> {
> public:
> bool has_value() const { return !!m_value; }
> T& value() { ensure(); return m_value.value(); }
>
> //bool has_error() const { return !!m_error; }
> const error_code& error() const { return m_error; }
>
> //bool has_exception() const { return !!m_exception; }
> const exception_ptr& exception() const { return m_exception; }
>
> void ensure() const
> {
> if (m_exception) { rethrow_exception(m_exception); }
> if (m_error) { throw system_error(m_error); }
> }
>
> void set(none_t)
> {
> m_value = none;
> m_error = error_code();
> m_exception = nullptr;
> }
> void set(const T& val)
> {
> m_value = val;
> m_error = error_code();
> m_exception = nullptr;
> }
> void set(error_code err)
> {
> m_value = none;
> m_error = err;
> m_exception = nullptr;
> }
> void set(exception_ptr ep)
> {
> m_value = none;
> m_error = ep ? error_code(errc::has_exception) : error_code();
> m_exception = ep;
> }
>
> private:
> optional<T> m_value;
> error_code m_error;
> exception_ptr m_exception;
> };


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk