Discussion:
[outcome] To variant, or not to variant?
(too old to reply)
Gavin Lambert via Boost
2017-05-31 08:26:04 UTC
Permalink
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;
};

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;
};

(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.)

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.)

The obvious downside is that it now uses more storage, which might
hinder some inlining cases.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-05-31 08:44:09 UTC
Permalink
Post by Gavin Lambert via Boost
template<typename T>
class outcome
{
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;
}
optional<T> m_value;
error_code m_error;
exception_ptr m_exception;
};
(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.
In your mental model what is the interpretation of the situation when you
have both an error_code and an exception_ptr?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert via Boost
2017-05-31 23:06:54 UTC
Permalink
Post by Andrzej Krzemienski via Boost
Post by Gavin Lambert via Boost
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.
In your mental model what is the interpretation of the situation when you
have both an error_code and an exception_ptr?
Mostly where error() returns the error_code and exception() returns the
system_error(error()), so that the caller could choose to treat it
either way, as you'd expect.

Conceivably there could be cases where someone might want to have a
different type of exception (eg. errc::invalid_argument plus
std::out_of_range or a derived type, which might convey some additional
information), although that could also be a can of worms best left unopened.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-01 07:29:58 UTC
Permalink
Post by Gavin Lambert via Boost
Post by Andrzej Krzemienski via Boost
Post by Gavin Lambert via Boost
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.
In your mental model what is the interpretation of the situation when you
have both an error_code and an exception_ptr?
Mostly where error() returns the error_code and exception() returns the
system_error(error()), so that the caller could choose to treat it either
way, as you'd expect.
Conceivably there could be cases where someone might want to have a
different type of exception (eg. errc::invalid_argument plus
std::out_of_range or a derived type, which might convey some additional
information), although that could also be a can of worms best left unopened.
So, are you saying that exception() and error() observe the same "failure
report", just in two different ways?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert via Boost
2017-06-01 08:25:06 UTC
Permalink
Post by Andrzej Krzemienski via Boost
Post by Gavin Lambert via Boost
Post by Andrzej Krzemienski via Boost
In your mental model what is the interpretation of the situation when you
have both an error_code and an exception_ptr?
Mostly where error() returns the error_code and exception() returns the
system_error(error()), so that the caller could choose to treat it either
way, as you'd expect.
Conceivably there could be cases where someone might want to have a
different type of exception (eg. errc::invalid_argument plus
std::out_of_range or a derived type, which might convey some additional
information), although that could also be a can of worms best left unopened.
So, are you saying that exception() and error() observe the same "failure
report", just in two different ways?
Of course. It's a single object containing the single outcome of a
method call. There might be multiple related sub-states but it's still
a single event with a single result.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Vicente J. Botet Escriba via Boost
2017-05-31 10:31:01 UTC
Permalink
Post by Gavin Lambert via Boost
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
template<typename T>
class outcome
{
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; }
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.
Post by Gavin Lambert via Boost
template<typename T>
class outcome
{
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;
}
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>>
Post by Gavin Lambert via Boost
(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.
Post by Gavin Lambert via Boost
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.
Post by Gavin Lambert via Boost
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


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/
Niall Douglas via Boost
2017-05-31 13:15:21 UTC
Permalink
Post by Vicente J. Botet Escriba via Boost
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;
Surely it is actually:

variant<monostate, T, error_code, exception_ptr>

std::monostate at the front of the typelist gives std::variant
default-construct-to-empty semantics.
Post by Vicente J. Botet Escriba via Boost
Post by Gavin Lambert via Boost
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.
Personally speaking, I have not found Lawrence's status_value proposal
sufficiently value-adding over returning a std::pair to be worth
implementing. Also, the STL already uses std::pair throughout for status
returns, it's the convention, though I will agree to use it is mildly
clunky e.g. unordered_map::insert().first.

Now, that said, if expected<T, E> and status_value<Status, Value> could
be combined into a single object, that I can see significant value in.
It's something I've often pondered for Outcome as well because it could,
if done right, let me eliminate error_code_extended.

The main thing which has stopped me is the potential confusion. For
example, right now we have result<T> which can be
empty|T|error_code_extended. If I instead made:

template<class T, class Payload = void> class result;

Now result could be (empty|T|std::error_code U Payload). But if this
review to date with 600+ emails has been complicated, imagine a review
of such a payload-carrying result object?

std::error_code + payload is obvious. But, what does an empty state +
payload mean? I have no idea.

Indeed, what does a T state + payload mean? I guess that is Lawrence's
status_value<> use case, but the problem is that the Payload type is
fixed between variant states of empty|T|std::error_code and that surely
is not particularly useful, you'd want different payload types with each
of T or std::error_code. That's why I didn't implement it.

Still, I'd be interested in what people think. The other option is
empty|T|status_value<std::error_code, Payload>, that would make more
sense, but at the potential cost of bloating the stack significantly
which could surprise end users in a way error_code_extended can not.

Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Vicente J. Botet Escriba via Boost
2017-05-31 17:06:51 UTC
Permalink
Post by Niall Douglas via Boost
Post by Vicente J. Botet Escriba via Boost
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;
variant<monostate, T, error_code, exception_ptr>
std::monostate at the front of the typelist gives std::variant
default-construct-to-empty semantics.
optional<T> also give you that :)
Post by Niall Douglas via Boost
Post by Vicente J. Botet Escriba via Boost
Post by Gavin Lambert via Boost
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.
Personally speaking, I have not found Lawrence's status_value proposal
sufficiently value-adding over returning a std::pair to be worth
implementing. Also, the STL already uses std::pair throughout for status
returns, it's the convention, though I will agree to use it is mildly
clunky e.g. unordered_map::insert().first.
Things are changing in the standard. We want more explicit types.
status_value is a very good class that responds to real cases.
Post by Niall Douglas via Boost
Now, that said, if expected<T, E> and status_value<Status, Value> could
be combined into a single object, that I can see significant value in.
Why and how do you want to combine them. One is a sum type the other a
product type.
These classes are useful on its own use cases.
Post by Niall Douglas via Boost
It's something I've often pondered for Outcome as well because it could,
if done right, let me eliminate error_code_extended.
The main thing which has stopped me is the potential confusion. For
example, right now we have result<T> which can be
template<class T, class Payload = void> class result;
Now result could be (empty|T|std::error_code U Payload). But if this
review to date with 600+ emails has been complicated, imagine a review
of such a payload-carrying result object?
std::error_code + payload is obvious. But, what does an empty state +
payload mean? I have no idea.
Indeed, what does a T state + payload mean? I guess that is Lawrence's
status_value<> use case, but the problem is that the Payload type is
fixed between variant states of empty|T|std::error_code and that surely
is not particularly useful, you'd want different payload types with each
of T or std::error_code. That's why I didn't implement it.
You lost me.
Post by Niall Douglas via Boost
Still, I'd be interested in what people think. The other option is
empty|T|status_value<std::error_code, Payload>, that would make more
sense, but at the potential cost of bloating the stack significantly
which could surprise end users in a way error_code_extended can not.
You lost me definitely.

status_value is a product type because we need some additional
information even when the operation succeeds. The status is there to
convey this information, but conveys also the information explaining why
the result is not there.

status_value<S,T> could be seen as pair<variant<Sucsess, Failure>,
optional<T>>

There is an invariant Success <=> optional present and Failure <=>
optional not present.

You could as well see it as variant<pair<Sucsess, T>, Failure> or
expected<pair<Sucsess, T>, Failure>


Vicente

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/ma
Andrzej Krzemienski via Boost
2017-06-01 07:26:42 UTC
Permalink
2017-05-31 19:06 GMT+02:00 Vicente J. Botet Escriba via Boost <
Post by Vicente J. Botet Escriba via Boost
Post by Niall Douglas via Boost
Post by Vicente J. Botet Escriba via Boost
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;
variant<monostate, T, error_code, exception_ptr>
std::monostate at the front of the typelist gives std::variant
default-construct-to-empty semantics.
optional<T> also give you that :)
Post by Niall Douglas via Boost
Perhaps this could even be exposed even more so that user code could
Post by Vicente J. Botet Escriba via Boost
Post by Gavin Lambert via Boost
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.
Personally speaking, I have not found Lawrence's status_value proposal
sufficiently value-adding over returning a std::pair to be worth
implementing. Also, the STL already uses std::pair throughout for status
returns, it's the convention, though I will agree to use it is mildly
clunky e.g. unordered_map::insert().first.
Things are changing in the standard. We want more explicit types.
status_value is a very good class that responds to real cases.
Post by Niall Douglas via Boost
Now, that said, if expected<T, E> and status_value<Status, Value> could
be combined into a single object, that I can see significant value in.
Why and how do you want to combine them. One is a sum type the other a
product type.
These classes are useful on its own use cases.
Post by Niall Douglas via Boost
It's something I've often pondered for Outcome as well because it could,
if done right, let me eliminate error_code_extended.
The main thing which has stopped me is the potential confusion. For
example, right now we have result<T> which can be
template<class T, class Payload = void> class result;
Now result could be (empty|T|std::error_code U Payload). But if this
review to date with 600+ emails has been complicated, imagine a review
of such a payload-carrying result object?
std::error_code + payload is obvious. But, what does an empty state +
payload mean? I have no idea.
Indeed, what does a T state + payload mean? I guess that is Lawrence's
status_value<> use case, but the problem is that the Payload type is
fixed between variant states of empty|T|std::error_code and that surely
is not particularly useful, you'd want different payload types with each
of T or std::error_code. That's why I didn't implement it.
You lost me.
Post by Niall Douglas via Boost
Still, I'd be interested in what people think. The other option is
empty|T|status_value<std::error_code, Payload>, that would make more
sense, but at the potential cost of bloating the stack significantly
which could surprise end users in a way error_code_extended can not.
You lost me definitely.
status_value is a product type because we need some additional information
even when the operation succeeds. The status is there to convey this
information, but conveys also the information explaining why the result is
not there.
status_value<S,T> could be seen as pair<variant<Sucsess, Failure>,
optional<T>>
There is an invariant Success <=> optional present and Failure <=>
optional not present.
You could as well see it as variant<pair<Sucsess, T>, Failure> or
expected<pair<Sucsess, T>, Failure>
What the OP means is that status_value can bee seen as a "superset" of
expected. It can cover all cases `expected` covers plus more. Of course,
this "more" is not necessarily better or harmless.

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes:
Vicente J. Botet Escriba via Boost
2017-06-01 17:20:38 UTC
Permalink
Post by Andrzej Krzemienski via Boost
2017-05-31 19:06 GMT+02:00 Vicente J. Botet Escriba via Boost <
Post by Vicente J. Botet Escriba via Boost
Post by Niall Douglas via Boost
It's something I've often pondered for Outcome as well because it could,
if done right, let me eliminate error_code_extended.
The main thing which has stopped me is the potential confusion. For
example, right now we have result<T> which can be
template<class T, class Payload = void> class result;
Now result could be (empty|T|std::error_code U Payload). But if this
review to date with 600+ emails has been complicated, imagine a review
of such a payload-carrying result object?
std::error_code + payload is obvious. But, what does an empty state +
payload mean? I have no idea.
Indeed, what does a T state + payload mean? I guess that is Lawrence's
status_value<> use case, but the problem is that the Payload type is
fixed between variant states of empty|T|std::error_code and that surely
is not particularly useful, you'd want different payload types with each
of T or std::error_code. That's why I didn't implement it.
You lost me.
Post by Niall Douglas via Boost
Still, I'd be interested in what people think. The other option is
empty|T|status_value<std::error_code, Payload>, that would make more
sense, but at the potential cost of bloating the stack significantly
which could surprise end users in a way error_code_extended can not.
You lost me definitely.
status_value is a product type because we need some additional information
even when the operation succeeds. The status is there to convey this
information, but conveys also the information explaining why the result is
not there.
status_value<S,T> could be seen as pair<variant<Sucsess, Failure>,
optional<T>>
There is an invariant Success <=> optional present and Failure <=>
optional not present.
You could as well see it as variant<pair<Sucsess, T>, Failure> or
expected<pair<Sucsess, T>, Failure>
What the OP means is that status_value can bee seen as a "superset" of
expected. It can cover all cases `expected` covers plus more. Of course,
this "more" is not necessarily better or harmless.
I will not say is a super set as expected<pair<Success, T>, Failure>
could be used instead of status_value.
I will say just it provides a different interface and can be more
adapted to people that use to use status with positive and negative meaning.

Vicente

_______________________________________________
Unsubscribe & other changes: http:/
Gavin Lambert via Boost
2017-05-31 23:15:04 UTC
Permalink
Post by Vicente J. Botet Escriba via Boost
Does none_t mean success or failure?
Either; that's up to the method in question. It also wasn't really the
focus of the question so you can pretend it doesn't exist if that makes
you happier.
Post by Vicente J. Botet Escriba via Boost
For what I see it means failure as it is not the result of value().
That was a consequence of trying to keep it returning by reference as
some people seem to prefer. If it returned by value then it could
return an optional<T>, which might be better.
Post by Vicente J. Botet Escriba via Boost
In addition it default construct to none_t.
That was intentional.
Post by Vicente J. Botet Escriba via Boost
Post by Gavin Lambert via Boost
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?
No, that was the entire point: to store all of them, and not as a variant.
Post by Vicente J. Botet Escriba via Boost
I could understand
varaint<optional<T>, variant<error_code, exception_ptr>>
Nested variants are utterly pointless. In an ideal world variant would
automatically collapse nested sub-variants to reduce storage, although
that does unfortunately complicate return-by-reference semantics.
Post by Vicente J. Botet Escriba via Boost
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.
That's closer to my second suggestion, yes, although I think using
pair<> is a disservice. I also don't think error_code and exception_ptr
are necessarily exclusive.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Daniela Engert via Boost
2017-05-31 10:59:04 UTC
Permalink
Post by Gavin Lambert via Boost
template<typename T>
class outcome
{
...
variant<none_t, T, error_code, exception_ptr> m_storage;
};
This is pretty much what I expect from an 'outcome' vocabulary type. In
my mental model, a 'none_t' would cover the 'partially formed' nascent
and/or forgot-to-produce-an-outcome state which is never looked at
besides contract checking. The failure to produce a T (i.e. violation of
postcondition) is reflected in error_code or exception_ptr.

Ciao
Dani
--
PGP/GPG: 2CCB 3ECB 0954 5CD3 B0DB 6AA0 BA03 56A1 2C4638C5

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Asbjørn via Boost
2017-05-31 11:26:59 UTC
Permalink
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.
I'm just a plain C++ user, and my gut is leaning strongly towards the
variant-like storage. However...
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.
This sort of thing is very common in system APIs as you mention. So if outcome
is meant to be able to wrap those "directly" then clearly it needs to be able to
do both.

But for these "non-error error results", I think I would prefer the code
wrapping it in an outcome to put the non-error bits as part of the value. For
example, a "here's some data, but I got more" error code could be handled by
outcome's value storing a <value_t, bool> tuple or similar rather than just value_t.

A truncated output has always been a proper error in my world, and handled as
such (fex retrying after increasing buffer).

Then again, just a plain user so :)

Cheers
- Asbjørn

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/bo
Peter Dimov via Boost
2017-05-31 14:26:22 UTC
Permalink
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
...
...

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
{
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; }
variant<none_t, T, error_code, exception_ptr> m_storage;
};
template<typename T>
class outcome
{
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;
}
optional<T> m_value;
error_code m_error;
exception_ptr m_exception;
};
_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Thomas Heller via Boost
2017-05-31 14:43:18 UTC
Permalink
Am 31.05.2017 4:26 nachm. schrieb "Peter Dimov via Boost" <
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
...

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()


Why not this:
make_exception_ptr(system_error(error()) when has_error()?

// exception when has_exception()
exception_ptr exception() const noexcept;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-05-31 15:17:20 UTC
Permalink
Post by Thomas Heller via Boost
Post by Peter Dimov via Boost
// nullptr when has_value()
// either nullptr or make_exception_ptr(system_error()) when has_error()
make_exception_ptr(system_error(error()) when has_error()?
Yes, this was what I meant, sorry. Shortened it too much.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-05-31 15:04:20 UTC
Permalink
Post by Peter Dimov via Boost
Post by Gavin Lambert via Boost
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
...
...
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;
What when `if_empty() == true`?
Post by Peter Dimov via Boost
// nullptr when has_value()
// either nullptr or make_exception_ptr(system_error()) when has_error()
// exception when has_exception()
exception_ptr exception() const noexcept;
What when `if_empty() == true`?
Post by Peter Dimov via Boost
// nullptr when !has_value(), otherwise &value_
T* operator->() noexcept;
T const* operator->() const noexcept;
This is not narrow: this is wide. I disagree.
Post by Peter Dimov via Boost
// *operator->()
T& operator() & noexcept;
T const& operator() const & noexcept;
T&& operator() && noexcept;
T const&& operator() const && noexcept;
This is operator*, right? This is technically narrow, but without essential
benefits of direct narrow contract, as I tried to explain in another
thread. I disagree.
Post by Peter Dimov via Boost
value() can also have the four-overload form, not shown for brevity.
Is there anyone that objects to that model?
Yes, it des not offer a *proper* narrow contract.
Post by Peter Dimov via Boost
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().
I am not sure about this. I have no strong opinion at this point.
Post by Peter Dimov via Boost
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.
What about is_empty()? Or are we considering the type without empty state?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-05-31 15:19:05 UTC
Permalink
Post by Andrzej Krzemienski via Boost
What when `if_empty() == true`?
Nothing, I don't have an empty state.
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
// nullptr when !has_value(), otherwise &value_
T* operator->() noexcept;
T const* operator->() const noexcept;
This is not narrow: this is wide. I disagree.
Why do you disagree?
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
// *operator->()
T& operator() & noexcept;
T const& operator() const & noexcept;
T&& operator() && noexcept;
T const&& operator() const && noexcept;
This is operator*, right? This is technically narrow, but without
essential benefits of direct narrow contract, as I tried to explain in
another thread. I disagree.
What essential benefits are lost?


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-01 07:03:29 UTC
Permalink
Post by Andrzej Krzemienski via Boost
What when `if_empty() == true`?
Nothing, I don't have an empty state.
Ok. Not having an empty state would work for me. I am not sure about others.
Post by Andrzej Krzemienski via Boost
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
// nullptr when !has_value(), otherwise &value_
T* operator->() noexcept;
T const* operator->() const noexcept;
This is not narrow: this is wide. I disagree.
Why do you disagree?
Post by Andrzej Krzemienski via Boost
// *operator->()
Post by Peter Dimov via Boost
T& operator() & noexcept;
T const& operator() const & noexcept;
T&& operator() && noexcept;
T const&& operator() const && noexcept;
This is operator*, right? This is technically narrow, but without
essential benefits of direct narrow contract, as I tried to explain in
another thread. I disagree.
What essential benefits are lost?
I tried to outline my reasoning in this post:
http://boost.2283326.n4.nabble.com/outcome-narrow-contract-wide-contract-and-value-if-td4695003.html

In short, when the narrow contract is directly in the library's interface,
I have a place where I can put BOOST_ASSERT(), or similar code for
assisting instrumented builds.

In what you propose, the library has a wide contract, it returns another
type (pointer) with a narrow contract; but it is somebody else's narrow
contract and I cannot put this librarie's sanity checks.

Let me just mention here that the LWG recommendation, while it probably
makes sense for the Standard Library description, is not necessary in
Boost. We can just make narrow-contract functions noexcept.

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-01 12:15:51 UTC
Permalink
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
Post by Peter Dimov via Boost
// *operator->()
Post by Peter Dimov via Boost
T& operator() & noexcept;
T const& operator() const & noexcept;
T&& operator() && noexcept;
T const&& operator() const && noexcept;
This is operator*, right? This is technically narrow, but without
essential benefits of direct narrow contract, as I tried to explain in
another thread. I disagree.
What essential benefits are lost?
http://boost.2283326.n4.nabble.com/outcome-narrow-contract-wide-contract-and-value-if-td4695003.html
In short, when the narrow contract is directly in the library's interface,
I have a place where I can put BOOST_ASSERT(), or similar code for
assisting instrumented builds.
That's what I was thinking - that you want to place an assert there - I just
wanted to confirm that this is the only objection. Do we agree that
returning nullptr from operator-> is good for informing the static analyzer
and the optimizer that a value is present?

result<X> r;

// ...

r->x; // same as __builtin_assume( has_value() )

X x = *r; // ditto

I can understand the desire to assert() inside, but at the same time, view
the idea of returning a X* to something that is not an X as monumentally
misguided in an error handling library. The goal here is to help people
write robust code, not introduce subtle bugs and security vulnerabilities by
allowing them scribble all over the stack.

(Note that with the above spec, you can still assert in op* and it would be
conforming, and you can still have a mode in which you assert in op->, but
it will not conform.)

The root of our disagreement is the idea that undefined behavior is good
because it supposedly allows you to do this or that. It isn't, it never is.
Defining the good things that we want to happen is good. Not defining them
in the hope they'll occur is not.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert via Boost
2017-06-01 23:38:09 UTC
Permalink
Post by Peter Dimov via Boost
That's what I was thinking - that you want to place an assert there - I
just wanted to confirm that this is the only objection. Do we agree that
returning nullptr from operator-> is good for informing the static
analyzer and the optimizer that a value is present?
I assume you meant "not present" there.

Having an explicit assert is still probably better than just returning
nullptr and hoping for a static analyzer to pick up on that (or to crash
at runtime). There are some platforms where unfortunately addresses
around zero are valid (and while the standards allow the compiler to
make nullptr not zero in this case, I don't know of any compilers that
actually do so, probably because it's hard to actually do that alongside
code that assumes it can memset(0) to get null pointers).

I think the assert should be in there regardless; so then the question
becomes whether you just reinterpret_cast<> after that or if you check
type and return nullptr if you somehow survive the assert.

It's not even really a narrow vs. wide contract thing, because it's
never well-defined behaviour for -> to return nullptr.

If assertions are disabled then both of these behaviours are UB (since
returning nullptr from -> will result in dereferencing nullptr, which is
inherently UB), but the second one is at least more likely to crash on
those platforms that don't have valid memory around 0, and that's a good
thing.

On the other hand, some people would argue that the extra branch is
pointless, especially in contexts where you check up front once and then
use -> multiple times, which is not an uncommon pattern. (Sure, you
could use * once and capture a reference, then avoid the issue entirely,
but that can make the code look more awkward.)


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-02 00:43:57 UTC
Permalink
Post by Gavin Lambert via Boost
Post by Peter Dimov via Boost
That's what I was thinking - that you want to place an assert there - I
just wanted to confirm that this is the only objection. Do we agree that
returning nullptr from operator-> is good for informing the static
analyzer and the optimizer that a value is present?
I assume you meant "not present" there.
No, I do mean "present". If you do `r->x` in your code, the compiler from
this point on will optimize based on the assumption that a value was
present - otherwise what you did would have been undefined.

Actually, it will even propagate the "has_value()" assumption backwards from
the `r->x` point, not just forwards.

Returning a reinterpret_cast'ed pointer, on the other hand, will not do so,
although maybe an `assert( has_value() )` will, depending. (Last time I
recall Microsoft tried feeding asserts to the optimizer, they reverted it,
as it broke too much code, but things may have changed in the meantime.)


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Vicente J. Botet Escriba via Boost
2017-06-03 06:31:37 UTC
Permalink
Post by Peter Dimov via Boost
Post by Peter Dimov via Boost
That's what I was thinking - that you want to place an assert there
- I > just wanted to confirm that this is the only objection. Do we
agree that > returning nullptr from operator-> is good for informing
the static > analyzer and the optimizer that a value is present?
I assume you meant "not present" there.
No, I do mean "present". If you do `r->x` in your code, the compiler
from this point on will optimize based on the assumption that a value
was present - otherwise what you did would have been undefined.
Actually, it will even propagate the "has_value()" assumption
backwards from the `r->x` point, not just forwards.
Returning a reinterpret_cast'ed pointer, on the other hand, will not
do so, although maybe an `assert( has_value() )` will, depending.
(Last time I recall Microsoft tried feeding asserts to the optimizer,
they reverted it, as it broke too much code, but things may have
changed in the meantime.)
Peter, I now that we are designing a class in C++11/.../C++17. I want to
know what do you think if we had contracts and that compilers (or static
analysis tools) where smart enough to detect UB.
Would you expect that operator-> return a nullptr when there is no value?
Or you want it to return nullptr because you believe that this could
help the current UB sanitizers or static analysis tools to detect some UB?

Best,
Vicente

P.S. Sorry fro the late responses; all you mails were considered as spams.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/
Andrzej Krzemienski via Boost
2017-06-02 07:44:32 UTC
Permalink
Post by Peter Dimov via Boost
Post by Peter Dimov via Boost
Post by Peter Dimov via Boost
// *operator->()
Post by Peter Dimov via Boost
T& operator() & noexcept;
T const& operator() const & noexcept;
T&& operator() && noexcept;
T const&& operator() const && noexcept;
This is operator*, right? This is technically narrow, but without >>
essential benefits of direct narrow contract, as I tried to explain in >>
another thread. I disagree.
Post by Peter Dimov via Boost
What essential benefits are lost?
http://boost.2283326.n4.nabble.com/outcome-narrow-contract-
wide-contract-and-value-if-td4695003.html
In short, when the narrow contract is directly in the library's
interface, I have a place where I can put BOOST_ASSERT(), or similar code
for assisting instrumented builds.
That's what I was thinking - that you want to place an assert there - I
just wanted to confirm that this is the only objection.
It is not _the only_ objection. I thought that this one would be the
easiest to communicate across.
Post by Peter Dimov via Boost
Do we agree that returning nullptr from operator-> is good for informing
the static analyzer and the optimizer that a value is present?
result<X> r;
// ...
r->x; // same as __builtin_assume( has_value() )
X x = *r; // ditto
I cannot answer this question with certainty. It looks like it has the
potential to address the static-analyzer/UB-sanitizer use cases. On the
other hand, static analyzers are not ideal, and they get lost when the
reason for UB spans across to wide space. It is conceivable that putting an
ASSERT(), or some such, earlier (directly in `outcome` could help detect
UB, but your trick with operator-> would not. But I have no data to support
this.
Post by Peter Dimov via Boost
I can understand the desire to assert() inside, but at the same time, view
the idea of returning a X* to something that is not an X ....
Here, the first disagreement. The way I see it is that the blame is on the
user side. I would put it, "requesting the call X* to something you know
not to be an X is misguided".
Post by Peter Dimov via Boost
as monumentally misguided in an error handling library.
A question to you. Is your verdict affected by the fact that this is "an
error handling library" or that this is a "vocabulary type"? IOW, would
your position be different if we were discussing a design of some bigger
library, like ASIO. In yet other words, do you object the same to
vector::operator[] allowing UB when over-indexed?

I fail to be convinced that "error handling library" should make different
decisions than any other library. It is my understanding that `outcome and
`expected` are intended to carry the information about unavailability of
resources, and similar conditions in the environment. In contrast, what we
are arguing about is what happens upon programmer using the library
incorrectly.
Post by Peter Dimov via Boost
The goal here is to help people write robust code,
Here is the second disagreement. That is, I do agree with you (and I guess
everybody agrees) that this is always the important goal: help people write
robust code. I just fail to see how the decision to assign *just any*
semantics to something that is a misguided decision (I mean "requesting the
call X* to something you know not to be an X") helps people write robust
code. It is not robust to requesting the call X* to something you know not
to be an X and you are not preventing that.
Post by Peter Dimov via Boost
not introduce subtle bugs
In general, you cannot protect form users planting bugs in their logic.
Post by Peter Dimov via Boost
and security vulnerabilities by allowing them scribble all over the stack.
So, is this your goal? In case the programmer does not know what he is
doing (that he is using the library incorrectly), the library itself should
take actions to minimize the damage? I am somewhat convinced by that.
Although I am not sure that this is the library that should take this
responsibility. In my view, the library should provide information what the
invalid usages are, and there should be another tool (like static analyzer)
that does the job of minimizing damage.
Post by Peter Dimov via Boost
(Note that with the above spec, you can still assert in op* and it would
be conforming,
Do you mean that you are ok with operator* having a narrow contract?
Post by Peter Dimov via Boost
and you can still have a mode in which you assert in op->, but it will not
conform.)
But then I do not understand what you get in this compromise that operator*
has narrow- and operator-> has a wide contract.

And we should also bare in mind Niall's opinion, that if he agrees to a
narrow contract, such function should spell longer than `value()`.
Post by Peter Dimov via Boost
The root of our disagreement is the idea that undefined behavior is good
because it supposedly allows you to do this or that.
It is not in this that I see the root of our disagreement. In fact, I do
not yet see where this root is. What I fight for is not an UB but narrow
contracts. The different between the two is the following. In order for UB
to occur you require two things: a function, call it `f` with a narrow
contract, and an another function that calls `f` out of the contract. In
the ideal world you have functions with narrow contracts and nobody calling
them out of contract. And defining contracts, as formally as possible, help
users identify when they are violating the contract.

Not every design requires narrow contract. For instance, if we allowed only
variant-like inspection of `expected`, every usage makes sense and is wide
in contract. But the design choice that you first ask in which of the
states `expected` is in, and then "get_the_T` and `get_the_E` allows for
incorrect usage. Narrow contract simply reflects the fact that there is a
way to use this interface incorrectly. Narrow contract is not incorrect, it
simply reflects the fact that there might exist incorrect programs.
Post by Peter Dimov via Boost
It isn't, it never is.
No, I do not believe that UB guarantees something in run-time, or rely on
it.
Post by Peter Dimov via Boost
Defining the good things that we want to happen is good. Not defining them
in the hope they'll occur is not.
I agree with this statement in isolation, but I think you misunderstood my
position about narrow contracts.

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-02 10:29:18 UTC
Permalink
Post by Andrzej Krzemienski via Boost
And we should also bare in mind Niall's opinion, that if he agrees to a
narrow contract, such function should spell longer than `value()`.
I don't mind all-narrow at all. Hard to accidentally get wrong.

I do object to mixed narrow and wide where narrow is quicker to type
than wide, and the object is being used to transport uncertainty. It
demands too much understanding of detail by the average programmer.

Regarding the debate about narrow contracts, if one bases
outcome/result/expected on std::variant as I believe one probably ought
to, then as std::variant doesn't offer narrow contracts, neither could
outcome/result/expected.

Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-02 12:02:04 UTC
Permalink
Andrzej Krzemienski wrote:
...
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
That's what I was thinking - that you want to place an assert there - I
just wanted to confirm that this is the only objection.
It is not _the only_ objection. I thought that this one would be the
easiest to communicate across.
What are the others?
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
I can understand the desire to assert() inside, but at the same time,
view the idea of returning a X* to something that is not an X ....
Here, the first disagreement. The way I see it is that the blame is on the
user side. I would put it, "requesting the call X* to something you know
not to be an X is misguided".
Where is the disagreement? The blame is on the user side. This does not
change what actually transpires.
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
as monumentally misguided in an error handling library.
A question to you. Is your verdict affected by the fact that this is "an
error handling library" or that this is a "vocabulary type"?
No, this being an error handling library just makes the argument easier.
Post by Andrzej Krzemienski via Boost
In yet other words, do you object the same to vector::operator[] allowing
UB when over-indexed?
Possibly, today. In the past, not. It depends on whether the optimizer is
good enough to avoid the repeated bounds check, and because of aliasing
concerns, it may not be likely that it will ever be able to optimize it out
in this case.

vector::operator[] accesses occur in tight inner loops, the check is hard to
optimize out, so we swallow the UB. Doesn't mean we have to like it, it's a
necessary evil here.
Post by Andrzej Krzemienski via Boost
I fail to be convinced that "error handling library" should make different
decisions than any other library.
It shouldn't, I already said the operator-> for smart pointers should behave
the same.
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
The goal here is to help people write robust code,
Here is the second disagreement. That is, I do agree with you (and I guess
everybody agrees) that this is always the important goal: help people
write robust code. I just fail to see how the decision to assign *just
any* semantics to something that is a misguided decision (I mean
"requesting the call X* to something you know not to be an X") helps
people write robust code.
"Robust" is not the same as "correct". Robust code means code that doesn't
misbehave when faced with unforeseen circumstances; having a logic error is
such one unforeseen circumstance. Nobody is perfect.
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
not introduce subtle bugs
In general, you cannot protect form users planting bugs in their logic.
And I do not. Accessing a null pointer crashes. I however can mitigate the
effects of their bugs, which is what I'm doing.
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
and security vulnerabilities by allowing them scribble all over the stack.
So, is this your goal? In case the programmer does not know what he is
doing (that he is using the library incorrectly), the library itself
should take actions to minimize the damage? I am somewhat convinced by
that.
Yes, exactly.

Here you have a union of an arbitrary type and std::error_code.
std::error_code contains a pointer to a type with a vtable. Allowing the
ability to write arbitrary data over that pointer is a security concern
because this could lead to arbitrary code execution.
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
(Note that with the above spec, you can still assert in op* and it would
be conforming,
Do you mean that you are ok with operator* having a narrow contract?
It already has. I have deliberately left it implicit though.
Post by Andrzej Krzemienski via Boost
And we should also bare in mind Niall's opinion, that if he agrees to a
narrow contract, such function should spell longer than `value()`.
Yes, there is that.
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
The root of our disagreement is the idea that undefined behavior is good
because it supposedly allows you to do this or that.
It is not in this that I see the root of our disagreement. In fact, I do
not yet see where this root is. What I fight for is not an UB but narrow
contracts.
Traditionally the two are synonyms in C++. If you have a narrow contract,
the behavior on contract violation is undefined. Can't have one without the
other.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-02 16:40:29 UTC
Permalink
Post by Peter Dimov via Boost
...
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
That's what I was thinking - that you want to place an assert there - I
just wanted to confirm that this is the only objection.
It is not _the only_ objection. I thought that this one would be the
easiest to communicate across.
What are the others?
In code reviews, when I see someone using the function out of contract, I
know that this someone has a bug.

When wide contracts are employed, even if people inadvertently do silly
things, I can never easily detect bugs, because they operate within
contract by definition. Now maybe they have bug, and maybe they just
learned a way to do a hacky usage of the "rescue semantics" (he effect of
artificially widening the contract).
Post by Peter Dimov via Boost
Post by Andrzej Krzemienski via Boost
I can understand the desire to assert() inside, but at the same time, >
view the idea of returning a X* to something that is not an X ....
Here, the first disagreement. The way I see it is that the blame is on
the user side. I would put it, "requesting the call X* to something you
know not to be an X is misguided".
Where is the disagreement? The blame is on the user side. This does not
change what actually transpires.
Post by Andrzej Krzemienski via Boost
as monumentally misguided in an error handling library.
A question to you. Is your verdict affected by the fact that this is "an
error handling library" or that this is a "vocabulary type"?
No, this being an error handling library just makes the argument easier.
In yet other words, do you object the same to vector::operator[] allowing
Post by Andrzej Krzemienski via Boost
UB when over-indexed?
Possibly, today. In the past, not. It depends on whether the optimizer is
good enough to avoid the repeated bounds check, and because of aliasing
concerns, it may not be likely that it will ever be able to optimize it out
in this case.
vector::operator[] accesses occur in tight inner loops, the check is hard
to optimize out, so we swallow the UB. Doesn't mean we have to like it,
it's a necessary evil here.
Should I read the above as saying that in case of vector it would cause too
much potential run-time overhead; whereas in case of `expected` it implies
the existence of function `open_file` in the vicinity, and compared to the
cost of that function, the cost of the defensive check is negligible? Is
that what you are saying?
Post by Peter Dimov via Boost
I fail to be convinced that "error handling library" should make different
Post by Andrzej Krzemienski via Boost
decisions than any other library.
It shouldn't, I already said the operator-> for smart pointers should
behave the same.
Post by Andrzej Krzemienski via Boost
The goal here is to help people write robust code,
Here is the second disagreement. That is, I do agree with you (and I
guess everybody agrees) that this is always the important goal: help people
write robust code. I just fail to see how the decision to assign *just any*
semantics to something that is a misguided decision (I mean "requesting the
call X* to something you know not to be an X") helps people write robust
code.
"Robust" is not the same as "correct". Robust code means code that doesn't
misbehave when faced with unforeseen circumstances; having a logic error is
such one unforeseen circumstance. Nobody is perfect.
Post by Andrzej Krzemienski via Boost
not introduce subtle bugs
In general, you cannot protect form users planting bugs in their logic.
And I do not. Accessing a null pointer crashes. I however can mitigate the
effects of their bugs, which is what I'm doing.
Post by Andrzej Krzemienski via Boost
and security vulnerabilities by allowing them scribble all over the >
stack.
So, is this your goal? In case the programmer does not know what he is
doing (that he is using the library incorrectly), the library itself should
take actions to minimize the damage? I am somewhat convinced by that.
Yes, exactly.
Here you have a union of an arbitrary type and std::error_code.
std::error_code contains a pointer to a type with a vtable. Allowing the
ability to write arbitrary data over that pointer is a security concern
because this could lead to arbitrary code execution.
Post by Andrzej Krzemienski via Boost
(Note that with the above spec, you can still assert in op* and it would
Post by Peter Dimov via Boost
be conforming,
Do you mean that you are ok with operator* having a narrow contract?
It already has. I have deliberately left it implicit though.
And we should also bare in mind Niall's opinion, that if he agrees to a
Post by Andrzej Krzemienski via Boost
narrow contract, such function should spell longer than `value()`.
Yes, there is that.
Post by Andrzej Krzemienski via Boost
The root of our disagreement is the idea that undefined behavior is good
Post by Peter Dimov via Boost
because it supposedly allows you to do this or that.
It is not in this that I see the root of our disagreement. In fact, I do
not yet see where this root is. What I fight for is not an UB but narrow
contracts.
Traditionally the two are synonyms in C++. If you have a narrow contract,
the behavior on contract violation is undefined. Can't have one without the
other.
Maybe this calls for a new kind of precondition specification: that calling
a function in given circumstances is formally incorrect (tools are allowed
to make use of this information), but the component still guarantees a
rescue action.

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-02 16:55:07 UTC
Permalink
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
That's what I was thinking - that you want to place an assert there -
I just wanted to confirm that this is the only objection.
It is not _the only_ objection. I thought that this one would be the
easiest to communicate across.
What are the others?
In code reviews, when I see someone using the function out of contract, I
know that this someone has a bug.
But this doesn't apply here, does it?

When you see r->x for !r, you do know that someone has a bug, right?
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
vector::operator[] accesses occur in tight inner loops, the check is
hard to optimize out, so we swallow the UB. Doesn't mean we have to like
it, it's a necessary evil here.
Should I read the above as saying that in case of vector it would cause
too much potential run-time overhead;
Not merely potential. Actual slowdown on the order of 100. You should read
it as "as much as we'd like to define the behavior of operator[], doing so
would be prohibitively expensive, so we won't."
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
Post by Andrzej Krzemienski via Boost
The root of our disagreement is the idea that undefined behavior is
good because it supposedly allows you to do this or that.
It is not in this that I see the root of our disagreement. In fact, I
do not yet see where this root is. What I fight for is not an UB but
narrow contracts.
Traditionally the two are synonyms in C++. If you have a narrow
contract, the behavior on contract violation is undefined. Can't have
one without the other.
Maybe this calls for a new kind of precondition specification: that
calling a function in given circumstances is formally incorrect (tools are
allowed to make use of this information), but the component still
guarantees a rescue action.
That's kind of what we all want, but there's at present no way of getting
there. Ideally -- let's assume we don't want to allow uses of the form
`r.operator->()` -- we would want `operator->` when `!r` to either invoke a
precondition violation handler that is guaranteed to terminate the program
and never return, or to return `nullptr`.

We have no tradition in expressing the above, so within the current
vocabulary I prefer guaranteeing the `nullptr` instead of leaving the
behavior undefined in the hope that it will end up being defined to the
above. (It won't be.)


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-02 17:08:39 UTC
Permalink
Post by Peter Dimov via Boost
Post by Peter Dimov via Boost
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
That's what I was thinking - that you want to place an assert there
- >> > I just wanted to confirm that this is the only objection.
Post by Peter Dimov via Boost
Post by Andrzej Krzemienski via Boost
It is not _the only_ objection. I thought that this one would be the
easiest to communicate across.
What are the others?
In code reviews, when I see someone using the function out of contract, I
know that this someone has a bug.
But this doesn't apply here, does it?
When you see r->x for !r, you do know that someone has a bug, right?
Yes, do. I am giving you the second reason why I want the narrow contract.
Within this second reason, I can live with your semantics. But previously I
have given you other reason, and there the operator-> trick will not work.
Post by Peter Dimov via Boost
vector::operator[] accesses occur in tight inner loops, the check is >
hard to optimize out, so we swallow the UB. Doesn't mean we have to like >
it, it's a necessary evil here.
Should I read the above as saying that in case of vector it would cause
too much potential run-time overhead;
Not merely potential. Actual slowdown on the order of 100. You should read
it as "as much as we'd like to define the behavior of operator[], doing so
would be prohibitively expensive, so we won't."
Why the same argument with "compiler will see that I am checking the same
condition twice, and remove redundant checks" does not apply here?
Post by Peter Dimov via Boost
Post by Peter Dimov via Boost
The root of our disagreement is the idea that undefined behavior is >
good because it supposedly allows you to do this or that.
Post by Andrzej Krzemienski via Boost
It is not in this that I see the root of our disagreement. In fact, I
do not yet see where this root is. What I fight for is not an UB but >>
narrow contracts.
Post by Peter Dimov via Boost
Traditionally the two are synonyms in C++. If you have a narrow >
contract, the behavior on contract violation is undefined. Can't have > one
without the other.
Maybe this calls for a new kind of precondition specification: that
calling a function in given circumstances is formally incorrect (tools are
allowed to make use of this information), but the component still
guarantees a rescue action.
That's kind of what we all want, but there's at present no way of getting
there. Ideally -- let's assume we don't want to allow uses of the form
`r.operator->()` -- we would want `operator->` when `!r` to either invoke a
precondition violation handler that is guaranteed to terminate the program
and never return, or to return `nullptr`.
Actually, now I am thinking not about Boost.Outcome but specifying these
preconditions in the Standard. If you want to std::terminate upon some
condition rather than let the program do random things, you could say.

*Requires:* `!r` with rescue action `std::terminate()`.
Post by Peter Dimov via Boost
We have no tradition in expressing the above, so within the current
vocabulary I prefer guaranteeing the `nullptr` instead of leaving the
behavior undefined in the hope that it will end up being defined to the
above. (It won't be.)
That is true, "it wont' be". And that was never the goal.

But what is the gain with the nullptr trick? someone can still cause UB
with it, so it does not seem much "safer". Are you increasing the chances
that it will be trapped by the operating system?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-02 17:24:42 UTC
Permalink
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
Not merely potential. Actual slowdown on the order of 100. You should
read it as "as much as we'd like to define the behavior of operator[],
doing so would be prohibitively expensive, so we won't."
Why the same argument with "compiler will see that I am checking the same
condition twice, and remove redundant checks" does not apply here?
Because we can test it and see that it doesn't. This is an empirical
argument, not a theoretical argument. "But it's the same here!" Well no, it
isn't, we can measure it.
Post by Andrzej Krzemienski via Boost
Post by Peter Dimov via Boost
We have no tradition in expressing the above, so within the current
vocabulary I prefer guaranteeing the `nullptr` instead of leaving the
behavior undefined in the hope that it will end up being defined to the
above. (It won't be.)
That is true, "it wont' be". And that was never the goal.
But what is the gain with the nullptr trick? someone can still cause UB
with it, so it does not seem much "safer". Are you increasing the chances
that it will be trapped by the operating system?
It's again a practical argument - we know what it does. In principle, it
should be the same, but it isn't. We know that if we write the specification
this way, so and so happens, and if we write it another way, different
things occur. :-)


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Vicente J. Botet Escriba via Boost
2017-06-03 06:52:06 UTC
Permalink
Post by Peter Dimov via Boost
Post by Peter Dimov via Boost
Not merely potential. Actual slowdown on the order of 100. You
should > read it as "as much as we'd like to define the behavior of
operator[], > doing so would be prohibitively expensive, so we won't."
Why the same argument with "compiler will see that I am checking the
same condition twice, and remove redundant checks" does not apply here?
Because we can test it and see that it doesn't. This is an empirical
argument, not a theoretical argument. "But it's the same here!" Well
no, it isn't, we can measure it.
Post by Peter Dimov via Boost
We have no tradition in expressing the above, so within the current
vocabulary I prefer guaranteeing the `nullptr` instead of leaving
the > behavior undefined in the hope that it will end up being
defined to the > above. (It won't be.)
That is true, "it wont' be". And that was never the goal.
But what is the gain with the nullptr trick? someone can still cause
UB with it, so it does not seem much "safer". Are you increasing the
chances that it will be trapped by the operating system?
It's again a practical argument - we know what it does. In principle,
it should be the same, but it isn't. We know that if we write the
specification this way, so and so happens, and if we write it another
way, different things occur. :-)
Peter, I not be against an implementation that returns nullptr of
operator-> as far as this is not documented and something the user could
use.
If returning a nulptr is the best thing that we can do today in order to
help the current tools to catch UB, why not.
I don't agree on documenting it, because this could make some user code
more complex.
When you have a narrow contract you know that the check must be done
before. If you document that it the user can be tempted to check it.
Well operator-> is particular in some way as the user doesn't use to use
it as x.operator->().
Consider for a moment that some compiler manage better UB when we do a
check and assert for unreachable code (as other are suggesting).
Requiring a nullptr as result will forbid this implementation, isn't it?

My question is, why do you consider that it is good to document it?

Vicente

_______________________________________________
Unsubscribe & other changes: http://lists.boost.or
Peter Dimov via Boost
2017-06-03 09:42:39 UTC
Permalink
Post by Vicente J. Botet Escriba via Boost
Peter, I not be against an implementation that returns nullptr of
operator-> as far as this is not documented and something the user could
use.
If returning a nulptr is the best thing that we can do today in order to
help the current tools to catch UB, why not.
I don't agree on documenting it, because this could make some user code
more complex.
When you have a narrow contract you know that the check must be done
before. If you document that it the user can be tempted to check it.
The expression `r->x` still has a narrow contract under my formulation. You
(and the static analyzer) still know that a check needs to be done. It's
just specified differently because the real world consequences of this
specification are more in line with the semantics I want to express.
Post by Vicente J. Botet Escriba via Boost
Well operator-> is particular in some way as the user doesn't use to use
it as x.operator->().
Yes, `r.operator->()` is now defined, which some consider desirable.
Presumably, if one writes `auto* p = r.operator->();`, one is aware of what
one's doing. I wouldn't presume that this code is a logic error without a
check, and neither should a static analyzer.
Post by Vicente J. Botet Escriba via Boost
Consider for a moment that some compiler manage better UB when we do a
check and assert for unreachable code (as other are suggesting). Requiring
a nullptr as result will forbid this implementation, isn't it?
No, it doesn't. This formulation just lifts the undefined behavior in `r->x`
from the library into user code, and what happens there depends on the
compiler. g++ takes care to trap on null pointer access, clang++ optimizes
out the checks under the assumption that null pointer accesses don't occur.
The balance here is hard to strike, because there are security implications
of just allowing `p->m = ...` to write over the error; at the same time,
there's an argument to be made to optimize correct programs more at the
expense of incorrect programs. The main feature of my formulation here is
that `r->x` and `p->x` (where p is a raw pointer) elicit a consistent
response from the compiler when they invoke UB, and are therefore covered by
the same optimization decisions or command line switches.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-03 10:00:35 UTC
Permalink
Post by Peter Dimov via Boost
Not merely potential. Actual slowdown on the order of 100. You should >
read it as "as much as we'd like to define the behavior of operator[], >
doing so would be prohibitively expensive, so we won't."
Why the same argument with "compiler will see that I am checking the same
condition twice, and remove redundant checks" does not apply here?
Because we can test it and see that it doesn't. This is an empirical
argument, not a theoretical argument. "But it's the same here!" Well no, it
isn't, we can measure it.
We have no tradition in expressing the above, so within the current >
vocabulary I prefer guaranteeing the `nullptr` instead of leaving the >
behavior undefined in the hope that it will end up being defined to the >
above. (It won't be.)
That is true, "it wont' be". And that was never the goal.
But what is the gain with the nullptr trick? someone can still cause UB
with it, so it does not seem much "safer". Are you increasing the chances
that it will be trapped by the operating system?
It's again a practical argument - we know what it does. In principle, it
should be the same, but it isn't. We know that if we write the
specification this way, so and so happens, and if we write it another way,
different things occur. :-)
Peter, I do not understand what you are trying to say here. You say you
have observed some useful behavior when your trick with operator-> is
applied. I am asking what it is. It is still an UB when used, so there must
be something to it. My guess would be that UB on null pointer is more
tool-friendly than UB on just any bad pointer. But if it should be the
case, why did you say you would like the same trick in shared_ptr? It deals
with dereferencing a nullptr both with and wothout your hack.

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-03 10:11:34 UTC
Permalink
Post by Andrzej Krzemienski via Boost
Peter, I do not understand what you are trying to say here. You say you
have observed some useful behavior when your trick with operator-> is
applied.
Trick?
Post by Andrzej Krzemienski via Boost
I am asking what it is. It is still an UB when used, so there must be
something to it. My guess would be that UB on null pointer is more
tool-friendly than UB on just any bad pointer. But if it should be the
case, why did you say you would like the same trick in shared_ptr? It
deals with dereferencing a nullptr both with and wothout your hack.
As I say in my reply to Vicente, specifying operator-> in this way leads to
consistent behavior for the expression `p->x` when p is a raw pointer, a
smart pointer, or, in this case, result<>/outcome<>. The same optimization
decisions apply, the same command-line compiler switches apply, in a
consistent manner.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-03 10:29:33 UTC
Permalink
Post by Andrzej Krzemienski via Boost
Peter, I do not understand what you are trying to say here. You say you
Post by Andrzej Krzemienski via Boost
have observed some useful behavior when your trick with operator-> is
applied.
Trick?
Sorry, I did not mean to imply any opinion or evaluation of quality. I just
needed a short phrase to refer to your proposed alternative. Maybe I should
call it "operator-> gives nullptr on bad access".

Regards,
&zej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-05-31 20:24:27 UTC
Permalink
Post by Peter Dimov via Boost
// 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 don't mind those operators being wide. I do object to them being
narrow as it's too little typing.
Post by Peter Dimov via Boost
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().
I would assume that if the programmer writes .error_raw() on a valued
object, they'd like to see a compiler warning.
Post by Peter Dimov via Boost
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.
Such a design would not allow identical code to work correctly with an
empty-capable or non-empty-capable objects.

It should be very simple, even simpler than yours:

.empty() true => object is empty
.has_value() true => value() observes a T
.has_error() true => error() returns the error code
.has_exception() true => exception() returns an exception_ptr to what
would be thrown if you called .value()

Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Robert Ramey via Boost
2017-05-31 16:09:38 UTC
Permalink
On 5/31/17 1:26 AM, Gavin Lambert via Boost wrote:

well since people are chiming in with their own ideas I might has well
demonstate mine.

The save numerics library (which I'm still working on trying to make all
the required changes), includes a type called checked_result. It turns
out that I needed this so I just made it. I was familar with
expected<T, E> and of course variant, but I didn't find a "finished"
implementation of expected - boost didn't have one, and variant, ... I
just didn't think of deriving from it - which would be my usual
inclination. Basically I thought my needs were just simple to need to
use a library or more general facility. After all, I'm a C++ programmer
and to a man with a hammer in his hand, the whole world looks like a
nail (not Naill). So I just wrote a simple class which I called
"checked_result". It's part of the safe numerics library implementation
so I didn't feel required to document it, but I did anyway because I
thought it would useful to do so, I have a simple easy to use
documentation system, and it's helpful for me to do this to clarify my
own thinking. Here a link to the documenation for checked_result.

https://htmlpreview.github.io/?https://raw.githubusercontent.com/robertramey/safe_numerics/master/doc/html/checked_result.html

and here is a link to the code

https://github.com/robertramey/safe_numerics/blob/master/include/checked_result.hpp

which comes to about 230 lines (way more than I thought it would). The
template includes a parameter for the value type but doesn't include one
for the error/exception type - because I didn't need it. I might
"improve" this in the future by making the error/exception type another
template parameter.

Readers of this humble missive are free to make of this what they desire.

When the review of outcome was announced, I had the expectation that the
proposal would be for something of like this of similar scale,
simplicity, and functionality - but done more formally and carefully.
So I'm sort of blown away at how this thing has grown. I've already
posted my review, so I'm not going to try to compare these things, I'm
just thinking that when something seems to grow way, way out of
proportion to what one original expectation, perhaps it's time to step
back and re-consider. Maybe the idea of "outcome" is too ambitious, or
maybe we're trying to solve too many problems at once, or maybe we're
trying to reconcile fundamentally irreconcilable view points.

Robert Ramey

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-01 07:22:38 UTC
Permalink
Post by Robert Ramey via Boost
well since people are chiming in with their own ideas I might has well
demonstate mine.
The save numerics library (which I'm still working on trying to make all
the required changes), includes a type called checked_result. It turns out
that I needed this so I just made it. I was familar with expected<T, E>
and of course variant, but I didn't find a "finished" implementation of
expected - boost didn't have one, and variant, ... I just didn't think of
deriving from it - which would be my usual inclination. Basically I
thought my needs were just simple to need to use a library or more general
facility. After all, I'm a C++ programmer and to a man with a hammer in
his hand, the whole world looks like a nail (not Naill). So I just wrote a
simple class which I called "checked_result". It's part of the safe
numerics library implementation so I didn't feel required to document it,
but I did anyway because I thought it would useful to do so, I have a
simple easy to use documentation system, and it's helpful for me to do this
to clarify my own thinking. Here a link to the documenation for
checked_result.
https://htmlpreview.github.io/?https://raw.githubusercontent
.com/robertramey/safe_numerics/master/doc/html/checked_result.html
and here is a link to the code
https://github.com/robertramey/safe_numerics/blob/master/
include/checked_result.hpp
which comes to about 230 lines (way more than I thought it would). The
template includes a parameter for the value type but doesn't include one
for the error/exception type - because I didn't need it. I might "improve"
this in the future by making the error/exception type another template
parameter.
Readers of this humble missive are free to make of this what they desire.
When the review of outcome was announced, I had the expectation that the
proposal would be for something of like this of similar scale, simplicity,
and functionality - but done more formally and carefully. So I'm sort of
blown away at how this thing has grown. I've already posted my review, so
I'm not going to try to compare these things, I'm just thinking that when
something seems to grow way, way out of proportion to what one original
expectation, perhaps it's time to step back and re-consider. Maybe the
idea of "outcome" is too ambitious, or maybe we're trying to solve too many
problems at once, or maybe we're trying to reconcile fundamentally
irreconcilable view points.
A valid observation. In a way `expected` and `outcome` (both from
Boost.Outcome) have different goals in mind. `expected` might in fact cover
your case. They could be two libraries. The reason they come together is
that they share 95% of the same implementation. They differ only by
interfaces. Two interfaces for two different problems.

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Robert Ramey via Boost
2017-06-01 14:23:08 UTC
Permalink
Post by Andrzej Krzemienski via Boost
A valid observation. In a way `expected` and `outcome` (both from
Boost.Outcome) have different goals in mind. `expected` might in fact cover
your case. They could be two libraries. The reason they come together is
that they share 95% of the same implementation.
I haven't investigated too deeply into the code so of course I didn't
know that. I would have "expected" that outcome, expected, et.al would
be derived from variant which would be the shared code. Also all the
questions about narrow/wide interface, no empty guarantees, etc. would
be resolved (for better or worse) in the variant library so the design,
review, maintenance, etc. of outcome, expected et al. would be confined
to the particular aspects of these components - thus being a more
economic application of limited brain surface area. Code size would be
smaller as well.
Post by Andrzej Krzemienski via Boost
They differ only by
interfaces. Two interfaces for two different problems
Right. It's common for different
"types/libraries/concepts/functions/etc." to factor out commonality for
just the reasons mentioned above.

Robert Ramey



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Vicente J. Botet Escriba via Boost
2017-06-01 17:24:08 UTC
Permalink
Post by Robert Ramey via Boost
Post by Andrzej Krzemienski via Boost
A valid observation. In a way `expected` and `outcome` (both from
Boost.Outcome) have different goals in mind. `expected` might in fact cover
your case. They could be two libraries. The reason they come together is
that they share 95% of the same implementation.
I haven't investigated too deeply into the code so of course I didn't
know that. I would have "expected" that outcome, expected, et.al
would be derived from variant which would be the shared code. Also
all the questions about narrow/wide interface, no empty guarantees,
etc. would be resolved (for better or worse) in the variant library so
the design, review, maintenance, etc. of outcome, expected et al.
would be confined to the particular aspects of these components - thus
being a more economic application of limited brain surface area. Code
size would be smaller as well.
The problem is that we don't have a never-empty variant in Boost (we
have Boost.Variant, but it uses double buffer IIRC).
Vicente

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinf
Robert Ramey via Boost
2017-06-01 17:43:33 UTC
Permalink
Post by Vicente J. Botet Escriba via Boost
The problem is that we don't have a never-empty variant in Boost (we
have Boost.Variant, but it uses double buffer IIRC).
Vicente
Right - so isn't variant the place to "fix" it? if outcome, expected,
optional ... need a never-empty guarantee, should variant (std, boost,
whatever) have that guarantee as well? Shouldn't these discussions take
place in the context of variant?

Now if the "best" implementation of variant isn't the "best" one for one
of it's derivatives - then I would ask why not? How are they different.
Of course this can windup as variant having a policy. I haven't seen
this but I would be very surprised if it hasn't come up. In any case it
seems that it would be much more efficient for these types to derive
from variant then repeat implementations - each with a little different
sauce. Not only more efficient as in reducing code. But more efficient
in consumption of my limited brain power:

OK I understand variant, never empty or not, etc...

template<class T>
using outcome = std::variant<T, std::error_code>

// + some global functions void f(const global &g){ do something }

or

template<class T>
struct outcome : public std::variant<T, std::error_code> {
// some special member functions
outcome(const std::error_code & e){...} // construct as error
outcome(const T & t){...} // construct legitimate result
};

(I'm obviously winging it here as I don't remember exactly what outcome
does.)

Basically, I'm arguing that all the discussion/design of these
"derivative" types belong in variant. Of course if there is not
agreement (not that this would ever happen) one could

template<class T, class V = std::variant>
using outcome = V<T, std::error_code>

In any case, I would be factoring out common code, design, rationale,
maintenance problems, documentation, etc up into the base class variant.

FWIW - this was my concern when I saw your presentation a few years ago.
Not that it was without merit, but I saw it as more of a use case for
variant rather than a new library component.

Robert Ramey



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-01 17:47:03 UTC
Permalink
Post by Robert Ramey via Boost
Right - so isn't variant the place to "fix" it? if outcome, expected,
optional ... need a never-empty guarantee, should variant (std, boost,
whatever) have that guarantee as well? Shouldn't these discussions take
place in the context of variant?
There were prolonged and furious debates on this topic while std::variant
was getting standardized, and the side who wanted a never-valueless variant
(that would be me) lost.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-01 18:47:12 UTC
Permalink
Post by Peter Dimov via Boost
Post by Robert Ramey via Boost
Right - so isn't variant the place to "fix" it? if outcome, expected,
optional ... need a never-empty guarantee, should variant (std, boost,
whatever) have that guarantee as well? Shouldn't these discussions
take place in the context of variant?
There were prolonged and furious debates on this topic while
std::variant was getting standardized, and the side who wanted a
never-valueless variant (that would be me) lost.
I haven't met a soul from WG21 who thinks variant should go empty as
frequently as the C++ 17 standard says it should.

I think it's acceptable if the user supplies types more than one of
which has a throwing move constructor. Otherwise you'd need to double
storage used.

If one or fewer types supplied to variant have throwing move
constructors, then a hard never-empty guarantee needs to be made, and
with some static flag or observer to let you know. Or just don't
implement .valueless_by_exception() :)

Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-01 18:30:05 UTC
Permalink
Post by Robert Ramey via Boost
or
template<class T>
struct outcome : public std::variant<T, std::error_code> {
// some special member functions
outcome(const std::error_code & e){...} // construct as error
outcome(const T & t){...} // construct legitimate result
};
Something like this?

https://github.com/pdimov/variant2/blob/develop/include/boost/variant2/result.hpp

https://godbolt.org/g/EFGkCj


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-01 18:49:43 UTC
Permalink
Post by Peter Dimov via Boost
Post by Robert Ramey via Boost
or
template<class T>
struct outcome : public std::variant<T, std::error_code> {
// some special member functions
outcome(const std::error_code & e){...} // construct as error
outcome(const T & t){...} // construct legitimate result
};
Something like this?
https://github.com/pdimov/variant2/blob/develop/include/boost/variant2/result.hpp
Hmm, maybe you could also implement expected<T, E>, and that would kinda
eliminate the need for a Boost.Outcome?

Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-01 18:55:39 UTC
Permalink
Post by Niall Douglas via Boost
Post by Peter Dimov via Boost
Something like this?
https://github.com/pdimov/variant2/blob/develop/include/boost/variant2/result.hpp
Hmm, maybe you could also implement expected<T, E>, and that would kinda
eliminate the need for a Boost.Outcome?
My original idea was to implement my idea of expected<T, E...>, but I took a
detour through never-valueless variant<> first.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-01 22:02:55 UTC
Permalink
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
Hmm, maybe you could also implement expected<T, E>, and that would
kinda eliminate the need for a Boost.Outcome?
My original idea was to implement my idea of expected<T, E...>, but I
took a detour through never-valueless variant<> first.
Would you be intending to submit your variant2 into Boost, along with an
expected<T, E...> implementation sometime soon?

You see, from my perspective, getting Outcome into Boost is nothing like
as important as getting **any** sort of variant-stored result<T> type
thing into Boost. I need one of those into Boost to make getting AFIO v2
into Boost feasible as every single AFIO API returns a result<T>.

In other words Peter, if you'd like to do the heavy lifting getting your
own Outcome implementation extending Variant2 into Boost instead of me
... I'm all for it.

I'll still be implementing my agreed changes to my own Outcome as lots
of my code depends on my specific formulation, and I don't want a Boost
dependency anyway. But most of the work of getting a Boost library past
review is not implementing and testing a high quality implementation,
it's all the other stuff like endless documentation and indeed, writing
300+ emails as part of a two week long review.

If I could skip all that other stuff because you were taking it on, I'd
be **overjoyed**. I could get back to working on AFIO v2 instead, a big
gain for me.

Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-01 22:54:02 UTC
Permalink
Post by Niall Douglas via Boost
Post by Peter Dimov via Boost
My original idea was to implement my idea of expected<T, E...>, but I
took a detour through never-valueless variant<> first.
Would you be intending to submit your variant2 into Boost, along with an
expected<T, E...> implementation sometime soon?
We'll see.
Post by Niall Douglas via Boost
You see, from my perspective, getting Outcome into Boost is nothing like
as important as getting **any** sort of variant-stored result<T> type
thing into Boost. I need one of those into Boost to make getting AFIO v2
into Boost feasible as every single AFIO API returns a result<T>.
In other words Peter, if you'd like to do the heavy lifting getting your
own Outcome implementation extending Variant2 into Boost instead of me ...
I'm all for it.
That's not and hasn't been my intention.

I wanted to write expected<T, E...> so that we have something concrete to
discuss regarding standardization, and for that, I needed a never-valueless
variant, which is also something that I would rather see in the standard at
some point, preferably instead of the current one.

The result and outcome classes I threw together are only toys at this point,
their purpose is both to let us see how the compilers optimize, and for me
to demonstrate how I would like the interface of
boost::result/boost::outcome/std::result/std::outcome to look like.

But I don't really want to enter these implementations in Boost instead of
yours. Your implementation has been tested in practice and is more
feature-rich. The error_code_extended infrastructure adds significant value,
and if you agree to a larger (or customizable at runtime) ring buffer and
chaining support, they'll become even better. (Attaching information to an
error code is a very good idea of yours.)

In short, I'd rather see your implementations in Boost, but with something
close to the interface I present - if the community can agree on it, of
course.

It's just hard to keep the design discussion focused without a reference
implementation. Ideally, we all say at some point OK, we're fine with that
interface, this is it, there we go.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Robert Ramey via Boost
2017-06-01 23:08:08 UTC
Permalink
Post by Peter Dimov via Boost
I wanted to write expected<T, E...> so that we have something concrete
to discuss regarding standardization, and for that, I needed a
never-valueless variant, which is also something that I would rather see
in the standard at some point, preferably instead of the current one.
I think we spend too much time thinking about the standard. Let us
focus on getting it right, getting it out there, getting people to use
it, getting feedback on it, making sure there is a version available
that is maintained, making sure it's well documented and plays well with
everything else. Let the standards committee spend their time catching
up to us. This has worked well in the past and I believe will continue
to work best.

I'm 69 years old as I write this. I could be dead before the it's all
standardized.

Robert Ramey


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-02 10:14:11 UTC
Permalink
Post by Peter Dimov via Boost
But I don't really want to enter these implementations in Boost instead
of yours. Your implementation has been tested in practice and is more
feature-rich. The error_code_extended infrastructure adds significant
value, and if you agree to a larger (or customizable at runtime) ring
buffer and chaining support, they'll become even better. (Attaching
information to an error code is a very good idea of yours.)
In short, I'd rather see your implementations in Boost, but with
something close to the interface I present - if the community can agree
on it, of course.
A shame. I already invested six months of effort taking a mature library
and getting it ready for review. I had been hoping to avoid more work on
this, AFIO languishes until Outcome is done.

Still that said, if Outcome changes significantly, better it happens now
before more AFIO code is hard wired around Outcome. Some of the AFIO
filesystem algorithms are quite subtle and rely very heavily on Outcome
behaving exactly just so.

Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-01 18:52:43 UTC
Permalink
Post by Peter Dimov via Boost
Post by Robert Ramey via Boost
or
template<class T>
struct outcome : public std::variant<T, std::error_code> {
// some special member functions
outcome(const std::error_code & e){...} // construct as error
outcome(const T & t){...} // construct legitimate result
};
Something like this?
https://github.com/pdimov/variant2/blob/develop/include/boost/variant2/result.hpp
https://godbolt.org/g/EFGkCj
And here's outcome for comparison:

https://github.com/pdimov/variant2/blob/develop/include/boost/variant2/outcome.hpp

https://godbolt.org/g/I4gM8D

The generated code does look a bit worse, the destructor is not optimized
out.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Robert Ramey via Boost
2017-06-01 22:07:16 UTC
Permalink
Post by Peter Dimov via Boost
Post by Robert Ramey via Boost
or
template<class T>
struct outcome : public std::variant<T, std::error_code> {
// some special member functions
outcome(const std::error_code & e){...} // construct as error
outcome(const T & t){...} // construct legitimate result
};
Something like this?
https://github.com/pdimov/variant2/blob/develop/include/boost/variant2/result.hpp
Probably I suppose ....
Post by Peter Dimov via Boost
https://godbolt.org/g/EFGkCj
But this isn't really my point. The point I'm trying to make is that
all the design disputes are also found in variant. It would seem to me
that we're repeating all the same arguments ... with pretty much similar
results. If the problem is that variant (std, boost, ...) is "broken"
or not suitable for that reason it would seem to me that THAT problem
would need to be addressed. Were that the case, then all the issues
with outcome, optional, result, expected would disappear. Maybe I'm
over simplifying here, so feel free to demonstrate I'm wrong. In any
case, I would think that if a consensus can't be agreed upon after all
this effort, maybe we should step back and except as a fact that
"concensus cannot be reached". So we'll permit variant implementations
of variant either with policy or simpler yet, just a separate
implementation. Of course these separate implementations would also
share a common base class so we get nested russian dolls. But at least
we don't get so much repetition.

Robert Ramey

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-02 10:09:15 UTC
Permalink
Post by Robert Ramey via Boost
But this isn't really my point. The point I'm trying to make is that
all the design disputes are also found in variant. It would seem to me
that we're repeating all the same arguments ... with pretty much similar
results. If the problem is that variant (std, boost, ...) is "broken"
or not suitable for that reason it would seem to me that THAT problem
would need to be addressed. Were that the case, then all the issues
with outcome, optional, result, expected would disappear. Maybe I'm
over simplifying here, so feel free to demonstrate I'm wrong. In any
case, I would think that if a consensus can't be agreed upon after all
this effort, maybe we should step back and except as a fact that
"concensus cannot be reached". So we'll permit variant implementations
of variant either with policy or simpler yet, just a separate
implementation. Of course these separate implementations would also
share a common base class so we get nested russian dolls. But at least
we don't get so much repetition.
There is one big difference with std::optional and std::variant - their
design is now **the standard**, for better or for worse.

All new code written henceforth ought to be designed around the C++
standard in my book, with hacks/workarounds as appropriate where the
standard object falls short.

But otherwise regarding discussion of Outcome, I think there are three
main use patterns for failure returning objects, and a single object
design can't fulfill more than two of them at best.

I personally believe that "single implementation, multiple
personalities" solves the problem, but I recognise that I have failed to
persuade anyone of that during this review, which was a surprise to me
actually. But finding out what others really care about, and you had no
idea that they did, is the whole point of a successful Boost review
irrespective of outcome.

Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-02 10:36:12 UTC
Permalink
Post by Niall Douglas via Boost
Post by Robert Ramey via Boost
But this isn't really my point. The point I'm trying to make is that
all the design disputes are also found in variant. It would seem to me
that we're repeating all the same arguments ... with pretty much similar
results. If the problem is that variant (std, boost, ...) is "broken"
or not suitable for that reason it would seem to me that THAT problem
would need to be addressed. Were that the case, then all the issues
with outcome, optional, result, expected would disappear. Maybe I'm
over simplifying here, so feel free to demonstrate I'm wrong. In any
case, I would think that if a consensus can't be agreed upon after all
this effort, maybe we should step back and except as a fact that
"concensus cannot be reached". So we'll permit variant implementations
of variant either with policy or simpler yet, just a separate
implementation. Of course these separate implementations would also
share a common base class so we get nested russian dolls. But at least
we don't get so much repetition.
There is one big difference with std::optional and std::variant - their
design is now **the standard**, for better or for worse.
All new code written henceforth ought to be designed around the C++
standard in my book, with hacks/workarounds as appropriate where the
standard object falls short.
But otherwise regarding discussion of Outcome, I think there are three
main use patterns for failure returning objects, and a single object
design can't fulfill more than two of them at best.
You have never put it this way. Can you list the three use patterns here?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-02 13:44:48 UTC
Permalink
Post by Andrzej Krzemienski via Boost
Post by Niall Douglas via Boost
All new code written henceforth ought to be designed around the C++
standard in my book, with hacks/workarounds as appropriate where the
standard object falls short.
But otherwise regarding discussion of Outcome, I think there are three
main use patterns for failure returning objects, and a single object
design can't fulfill more than two of them at best.
You have never put it this way. Can you list the three use patterns here?
Sure.

1. User wants failure reason type to be local to only the code it
applies to. So think custom E type, nonconvertible to any other E type,
and E is never error_code nor exception_ptr. They specifically want to
keep failure handling local a small patch of code e.g. constexpr
evaluation contexts.

All-narrow observers make sense for this use case as object usage is
tightly integrated to the code using it, and failure is handled entirely
locally.


2. User wants failure reason type to be globally understood and will be
choosing either E = std::error_code, or E = std::exception_ptr. They
specifically want failure handling to be able to traverse any or all
code, just like C++ exception throws. This is where Outcome was
primarily aimed at.

All-wide observers make sense for this use case, as due to handling a
wide degree of uncertainty, most failure handling code is *generic* i.e.
we test for some specific failure causes, and for everything else we
either ignore or hand it off to other code to handle.


3. User wants to write functional programming logic using the basic
vocabulary of Maybe, Either and i/o monads and basic operators of bind,
fmap, do etc and probably some subset or refinement of Hana for the
collections monads, though my GSoC student may be making the Ranges TS a
choice here as well later this summer.

All-narrow observers make sense for this use case as the monadic
operators ensure your function will never be called with the wrong state.


Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-02 14:57:15 UTC
Permalink
Post by Niall Douglas via Boost
3. User wants to write functional programming logic using the basic
vocabulary of Maybe, Either and i/o monads and basic operators of bind,
fmap, do etc and probably some subset or refinement of Hana for the
collections monads, though my GSoC student may be making the Ranges TS a
choice here as well later this summer.
All-narrow observers make sense for this use case as the monadic operators
ensure your function will never be called with the wrong state.
There's no need for narrow (value) observers when using the monadic
interface; it's all wide and since you get the value directly, you don't
need to observe it.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-02 21:51:55 UTC
Permalink
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
3. User wants to write functional programming logic using the basic
vocabulary of Maybe, Either and i/o monads and basic operators of
bind, fmap, do etc and probably some subset or refinement of Hana for
the collections monads, though my GSoC student may be making the
Ranges TS a choice here as well later this summer.
All-narrow observers make sense for this use case as the monadic
operators ensure your function will never be called with the wrong state.
There's no need for narrow (value) observers when using the monadic
interface; it's all wide and since you get the value directly, you don't
need to observe it.
Not always. Sometimes your function will be fed a wrapped value and
you'll need to observe it. I still think narrow makes more sense here.

I also forget another thing which use case 3 needs, and that is
immutability, which in turn probably in C++ implies that the types used
must be trivially destructible, else it would be unusable. In other
words, the code must be written as if able to run constexpr, probably.

Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Robert Ramey via Boost
2017-06-02 23:45:52 UTC
Permalink
Post by Niall Douglas via Boost
I also forget another thing which use case 3 needs, and that is
immutability, which in turn probably in C++ implies that the types used
must be trivially destructible, else it would be unusable. In other
words, the code must be written as if able to run constexpr, probably.
LOL - I've already run into problems with error_code due the fact that
it's constructor is not constexpr friendly. So I would guess you're on
to something here.
Post by Niall Douglas via Boost
Niall
_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Vicente J. Botet Escriba via Boost
2017-06-03 06:17:04 UTC
Permalink
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
3. User wants to write functional programming logic using the basic
vocabulary of Maybe, Either and i/o monads and basic operators of
bind, fmap, do etc and probably some subset or refinement of Hana for
the collections monads, though my GSoC student may be making the
Ranges TS a choice here as well later this summer.
All-narrow observers make sense for this use case as the monadic
operators ensure your function will never be called with the wrong state.
There's no need for narrow (value) observers when using the monadic
interface; it's all wide and since you get the value directly, you
don't need to observe it.
You are right Peter. You can just provide the Functor, Applicative,
Monad operations directly.
You can as well provide the SumType interface (to be defined) but that
will have at least the visit function.

My idea is to see any TypeConstructible PossiblyValued as a Functor, an
Applicative and a Monad.
expected<_,E>/optional<_>/unique_ptr<_,D>/shared_ptr<_> are all
PossiblyValued.
A PossiblyValued is the sum/variant of a type T and something else.

A TypeConstructible type is one that has a factory that makes the type
from its underlying type.

I know that see unique_ptr<_,D>/shared_ptr<_> as PossiblyValued types
can be surprising, but any NullablePointer either has a pointee (pointer
not null) or not (pointer null). So we can see it as the sum of T and
nullptr_t.

We don't need this and we can have direct mappings for all these
concrete types. Nevertheless, if a type has already access to its
possibly value types, why not have also a narrow contract.

The question is do we want optional/expected to provide only a monadic
interface. Do we want it to provide the visit function as well? Why not
provide a direct access?
I know (thanks to Niall) variant has a wide narrow access. We have lost
the train for std::variant.

If we had to define a monadic interface for std::variant<T,
unexpected<E>>, I'm sure that the std::variant implementer would have a
hidden narrow access to its elements and will not use neither get_if nor
visit. Well, this is what I will do to avoid to have to ensure/check
that there is no cost while using get_if/visit. The problem is that a
user of variant can not change variant or see hidden functions. Maybe
I'm wrong and I'm doing premature micro optimizations. While I think
compilers could optimize a lot of things, I believe that a developer
should try to define the minimal interface that helps the compiler to
generate the best code.

A narrow contract (like the one of optional, smart pointers) is like
accessing the alternative directly and this is could be considered bad.
The point is that it allows to build the high abstraction efficiently.

Again, we don't need a narrow contract access, but then we need all the
high level abstractions that we can built on to of it. I'm a fan of safe
interface, but also the ones that are efficient enough.

Vicente


P.S. Sorry if I have repeated myself more than once.

P.S. You can take a look at the current state of my work concerning this
a more at

https://github.com/viboes/std-make/tree/master/include/experimental/fundamental/v3

https://github.com/viboes/std-make/tree/master/doc/proposal




_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/
Peter Dimov via Boost
2017-06-03 09:49:57 UTC
Permalink
Post by Vicente J. Botet Escriba via Boost
If we had to define a monadic interface for std::variant<T,
unexpected<E>>, I'm sure that the std::variant implementer would have a
hidden narrow access to its elements and will not use neither get_if nor
visit. Well, this is what I will do to avoid to have to ensure/check that
there is no cost while using get_if/visit.
On gcc/clang there is no cost for using *get_if<> instead of a private
accessor, and depending on how get<> is written, there can be no cost to
using it as well. I did switch from get<> to private accessors within the
variant implementation, but that's mostly because I want to generate
aesthetically pleasing assembly on Compiler Explorer and not because there
is a measurable cost to using get<>.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Robert Ramey via Boost
2017-06-02 14:59:12 UTC
Permalink
Post by Niall Douglas via Boost
There is one big difference with std::optional and std::variant - their
design is now **the standard**, for better or for worse.
All new code written henceforth ought to be designed around the C++
standard in my book, with hacks/workarounds as appropriate where the
standard object falls short.
Ahhhh - I argued in a different thread that we should pay less attention
to the C++ standard. Traditionally, they've followed Boost. Most of
the stuff in the standard have been test driven in Boost first. Even
so, they make mistakes - as is likely has been done in this case.

We should strive to make good well conceived, well designed, well
implemented library components. Let them pick and choose what they want
to standardize. We don't work for them, the work for us - promulgating
the stuff we've already implemented and proven useful.

Robert Ramey

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-02 21:54:08 UTC
Permalink
Post by Robert Ramey via Boost
Post by Niall Douglas via Boost
There is one big difference with std::optional and std::variant - their
design is now **the standard**, for better or for worse.
All new code written henceforth ought to be designed around the C++
standard in my book, with hacks/workarounds as appropriate where the
standard object falls short.
Ahhhh - I argued in a different thread that we should pay less attention
to the C++ standard.
I'm not talking about future standards. I'm talking about the existing ones.

Ok, you can argue C++ 17 and its std::variant isn't standard *quite*
just yet, but I can guarantee you that std::variant will not be changing
in the C++ 17 standard (apart from defects).

Niall
--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Vicente J. Botet Escriba via Boost
2017-06-03 06:56:54 UTC
Permalink
Post by Peter Dimov via Boost
Post by Robert Ramey via Boost
or
template<class T>
struct outcome : public std::variant<T, std::error_code> {
// some special member functions
outcome(const std::error_code & e){...} // construct as error
outcome(const T & t){...} // construct legitimate result
};
Something like this?
https://github.com/pdimov/variant2/blob/develop/include/boost/variant2/result.hpp
https://godbolt.org/g/EFGkCj
Thanks Peter for working on this. We need such a variant2 at least in Boost.

As you show, building on top of a sound class makes the implementation
much simpler.

Best,
Vicente

_______________________________________________
Unsubscribe & other changes
Andrzej Krzemienski via Boost
2017-06-02 07:50:20 UTC
Permalink
Post by Robert Ramey via Boost
The problem is that we don't have a never-empty variant in Boost (we have
Boost.Variant, but it uses double buffer IIRC).
Vicente
Right - so isn't variant the place to "fix" it? if outcome, expected,
optional ... need a never-empty guarantee, should variant (std, boost,
whatever) have that guarantee as well? Shouldn't these discussions take
place in the context of variant?
outcome, expected, optional -- they all have a nice property, they always
have at least one type that is no-throw upon copy/move
constructor/assignment. Under these constraints it is easy to implement a
variant with strong assignment.

But generic variant needs to work on generic Ts, and cannot make this
assumption. If we provided a second variant, with stricter assumptions, or
added a specialization to the current one, it would work, I guess.

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Continue reading on narkive:
Loading...