First, I should also say thank you to Jens Maurer for writing that initial
reply to Niall's paper, since I wouldn't have looked at the paper if Jens
hadn't done so first. Even if it turned out that that paper is already
obsolete in Niall's view.
This thread is getting super tangled, so I won't try to respond directly to
any quotes in context; I'll just list some of my opinions in the order I
come to them. If something here looks like I'm agreeing-with or
contradicting something you personally said in this thread, then yeah, I'm
probably responding to you. ;)
(1) A default-constructed std::error_code is absolutely an "ok, no error"
value. In fact, for any enumeration type E where is_error_code_enum_v<E>,
the value static_cast<E>(0) must be the "ok, no error" value. I think we
are all in agreement that the Standard strongly implies that stuff breaks
if you deviate from this informal rule. Niall referred to the absence of
normative wording as "really a defect." Yes it is. I would like us all to
assume that the defect will be fixed, and waste no further verbiage on
pedantry in that area.
(2) Re a tricky idea Jens suggested: `make_error_code(E)` should not do any
value-munging "tricks" (such as remapping E(200) to int(0), or E(0) to
int(200)), because such tricks would should be undone by
`static_cast<E>(ec.code())`, and those semantics are 100% nailed down by
(3) It seems very important to Niall that the Expected/Result type that is
standardized should have a normatively defined class definition, so that
anyone could look at the Standard wording and deduce exactly how to
interoperate with Expected/Result from C code or from pre-C++2a code. I
sympathize with this idea. I think Nicol was right when he said that the
C++ Standard generally does *not* standardize class definitions. So just be
aware that you will be swimming upstream to get this â especially if you
want a non-standard-layout class with special member functions, private
members, etc etc. Jens gives the example of std::complex.
(4) I believe that the world would be a better place if the C++ Standard
*did* sometimes standardize class definitions. And the world would be
better if they *did* sometimes standardize a function's behavior via
(5) I still believe that it is a sin to subclass std::error_code,
std::exception_ptr, or any other std:: class type that is not already part
of a classical hierarchy. Composition/aggregation is okay. Inheritance is
not. You will get slicing and you will get bugs.
(6) Jens's example use-case #4 almost convinced me that
implicit-construction-from-the-E-type is desirable. `if (!cwd) return
cwd.error()` is easier on the eyes than `if (!cwd) return
make_unexpected(cwd.error())`. However, I wonder whether we could get the
best of both worlds by providing a convenience member function: `if (!cwd)
return cwd.unexpected()`. For the long-term problems caused by implicit
conversions involving vocabulary types, please see the ongoing fiasco of
std::string, std::string_view, std::filesystem::path. Eliminating implicit
conversions from your C++ code eliminates bugs. Eliminating implicit
conversions from your proposals eliminates literally YEARS of work for the
future members of the Committee.
(7) I don't like the term "never-empty variant." Even Anthony Williams'
P0110 (which explores the idea of making a variant twice as big as
std::variant so that the new object could always be constructed before the
old one was destroyed) finally admits that he made emplace<T>() destroy the
old object before constructing the new one, and that means that his variant
*also* has an empty state. A variant which *requires* an empty state to
exist is not "never-empty."
(8) I certainly hope that both Result<T,E> and Expected<T,E> are "X"
whenever both T and E are "X", for values of "X" including at least
"trivially copyable" and "trivially destructible". If some implementation
of Result or Expected does not provide those guarantees, then that's a QoI
issue against that implementation. (I hope that the version that gets
standardized will have normative wording *requiring* those guarantees. If
that happens, then implementations without those guarantees will be
(9) Niall wrote: "If you are adding a feature which uses 98% of an existing
feature, then the standard should require use of that existing feature." I
strongly disagree with that statement. Now, I *do* think (and I think Niall
might agree) that we should not add new features with small gratuitous
quirks that make them impossible to implement in terms of other standard
features. To take two examples of the "gratuitous quirk" problem:
std::packaged_task<R(A...)> is not implementable in terms of
std::function<R(A...)> because copyability, and std::vector<T,A>::resize is
not implementable in terms of std::uninitialized_move_if_noexcept() because
allocator_traits. Therefore, if it were truly *impossible* to implement
Expected<T,E> in terms of std::variant<T,E>, I'd complain.
HOWEVER, I repeat that no sane implementation should pull in <variant> as a
result of including <expected>. The former is a "higher-level" feature than
the latter; it includes a ton of TMP machinery that is not needed by
<expected>. I might expect both headers to include some "helper" header
providing just the common parts needed by both.
We see an example of this in both libstdc++ and libc++, where <set> and
<map> are implemented in terms of a "helper" header sometimes named
<__tree>. It is theoretically possible to implement std::map<K,V> in terms
of std::set<something_clever<K,V>>, but no sane implementation will pull in
all of <set> as a result of including <map>.
I hope this clarifies my position.
(10) Speaking of "levelization", I do like the idea of having
"higher-level" and "lower-level" headers in the STL. I think it's
unfortunate that <system_error> â which sounds like a low-level header â
actually depends on <string>, which recursively depends on most of the STL
(<string_view>, <ostream>, et cetera). I talked to Charley about this at
CppCon: I'm ambivalent. On the one hand I think it really sucks that
<system_error> has any dependencies at all. On the other hand, I appreciate
the idea of "vocabulary types," and it feels hypocritical to condemn the
library's own use of `std::string` as the vocabulary type for returning a
(11) Niall writes: "Outcome uses struct storage, not union storage."
The paper Jens and I were reading flatly contradicts that statement, on
page 4, where it says "Result requires the following layout to be
implemented:" and then shows a piece of code involving a union. However,
the reference C implementation
on page 14 uses a non-union struct, and the reference C++ implementation on
page 17 uses a "non-standard" optional<T> with the caveats listed in a
comment at the bottom of page 16. It would be much *much* clearer if the
new paper didn't imply the use of a union/optional/variant; it would save
Niall a lot of argumentation. If you want a standard-layout struct, just
use one! Don't even put "union" in your sample code!
(12) Niall writes: "E is not necessarily an error for Expected. It's merely
an 'unexpected'." This is a distinction without a difference. Whether we
call it an "error", an "unexpected result", or an "exceptional state", the
point is that it's a "disappointment" (to use Lawrence Crowl's term). The
user's expectations are that foo.value() will give them the normal-path
result (if any), and that foo.error() will give them the
"disappointment-path" result (if any). Jens points out, rightly, that if
the user asks for foo.error() and there is no disappointment to report, it
makes some sense to return "no error" as opposed to blowing up the program
via a thrown exception or via undefined behavior. As to the question of
whether we can generally consider a default-constructed E to indicate "no
error", please see my point (1) in this email.
(13) I don't see the usefulness of outcome<X,Y,Z>; but I know that if I
don't like it I can avoid using it, and it sounds like Niall is not
proposing that one for standardization anyway. (That is, he's proposing it
for Boost.Outcome but not for C++2a.)
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to email@example.com.
To post to this group, send email to firstname.lastname@example.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CADvuK0%2B4x1LUi%3DzAxDz2TY8af%3DGPxis5rnmcLLjYFTQe7fbQJg%40mail.gmail.com.