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() { return m_storage.get (); } bool has_exception() const { return m_storage.has
(); } exception_ptr& exception() { return m_storage.get (); } 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
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; };