Boost logo

Boost :

Subject: Re: [boost] [outcome] To variant, or not to variant?
From: Vicente J. Botet Escriba (vicente.botet_at_[hidden])
Date: 2017-05-31 10:31:01


Le 31/05/2017 à 10:26, Gavin Lambert via Boost a écrit :
> 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:
>
> 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;
> };
>
Does none_t mean success or failure?
For what I see it means failure as it is not the result of value().
In addition it default construct to none_t.

The mental model for me is
     variant<optional<T>, error_code, exception_ptr>> m_storage;

I know it is the same and your representation is more efficient.
> 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;
> };
>
I guess you want a variant here. You don't want to store all of them,
isn't it?

I could understand

     varaint<optional<T>, variant<error_code, exception_ptr>>

> (Don't get too hung up on the specifics. This is a sketch, not a real
> implementation. I've obviously omitted things that a real
> implementation would need such as more const methods and move support,
> and construction and assignment rather than using set methods. The
> focus is on variant vs. non-variant.)
>
> Anyway, the point is that this could actually transport multiple
> things; in particular as above both an error_code and an
> exception_ptr; perhaps set(error_code) could construct an exception as
> well, although I've explained elsewhere why I don't like that option.
>
> Perhaps this could even be exposed even more so that user code could
> explicitly provide both a value and an error -- think "here's a value,
> but it was truncated" or "here's the 5 values you asked for, but
> there's more", both fairly common in system APIs. Or for things like
> ambiguous matches where you still return the most likely candidate.
> Or dictionary insertion (duplicate key, but here's the value that's
> already there). Or many more such examples. (Though obviously in this
> case value() couldn't call ensure(). But that should make some people
> happy.)
>
This corresponds to the status_value [1] model where you store the
reason why you don't have a value or why you have it and an
optional<value>. As described by L. Crowl in [2], there are use cases
for status_value, expected and exceptions.

     pair<optional<T>, variant<error_code, exception_ptr>>

Where we have a value of error_code to mean success.

> Also note has_error() and has_exception() don't even need to be
> provided in this version since you can always call error() and
> exception() in a boolean context anyway, with the same result. (Or
> actually a slightly better result than the implementation shown here.)

I don't see why we need to choose.between the narrow and the wide
functions yet.
First you need to state what is your mental model. The provide the
operations.
>
> The obvious downside is that it now uses more storage, which might
> hinder some inlining cases.

I'm not sure of this possible missing optimization.

Best,
Vicente

[1] P0262R1 A Class for Status and Optional Value
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0262r1.html
[2] P0157R0 Handling Disappointment in C++
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0157r0.html


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