Implement std::expected https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p0323r12.html
Added tests
Differential D124516
[libc++] Implement `std::expected` P0323R12 huixie90 on Apr 27 2022, 3:33 AM. Authored by
Details
Implement std::expected https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p0323r12.html Added tests
Diff Detail
Unit Tests Event TimelineThere are a very large number of changes, so older changes are hidden. Show Older Changes
Comment Actions Please add a link to the paper in the change description. As well as a meaningful description for the change. Also there's no reason to split this up into multiple headers. There are no deps we can cut from other headers by doing so, nor any circular dependency issues we're working around. Another improvement that can be made is reducing the number of instantiations we perform in a lot of the metaprogramming. _And, _Not, and _Or should only be used when short circuiting is needed. In most cases here it isn't. Every single instantiation performed in expected is carried around by the compiler and in the AST for the entire translation unit. That's costly, lets avoid that cost.
Comment Actions
I've removed short circuiting for the static_assert cases. but most meta programming in this patch can benefit from short-circuiting as they are used in the constructors' constraints. (the most complicated one in this patch is the __can_convert @ldionne had some comments explaining why they are needed. (in the early revisions of this patch, there was no short-circuiting) That being said, I wrote sine simple tests that benchmark the compilation times. The first one is the case where short circuiting happens // The 3rd requirement of `__can_convert` // _Not<is_constructible<_Tp, expected<_Up, _Gp>&>> // fails and short circuits template <std::size_t N> struct Foo { Foo() {} template <std::size_t M> Foo(Foo<M>) {} template <std::size_t M, class E> Foo(std::expected<Foo<M>, E>) {} }; template <std::size_t N> void callCtor() { std::expected<Foo<N>, int> e1; [[maybe_unused]] std::expected<Foo<N + 1>, int> e2(e1); } template <std::size_t... Ns> void generateTest(std::index_sequence<Ns...>) { (callCtor<Ns>(), ...); } int main() { generateTest(std::make_index_sequence<1000>{}); }
So we see some benefits of short circuiting (out weighs the instantiation cost) Then I have another test case where all boolean requirements pass so no short circuiting happening // All requirements of `__can_convert` pass // short circuiting has no benefit template <std::size_t N> struct Foo { Foo() {} template <std::size_t M> Foo(Foo<M>) {} }; template <std::size_t N> void callCtor() { std::expected<Foo<N>, int> e1; [[maybe_unused]] std::expected<Foo<N + 1>, int> e2(e1); } template <std::size_t... Ns> void generateTest(std::index_sequence<Ns...>) { (callCtor<Ns>(), ...); } int main() { generateTest(std::make_index_sequence<1000>{}); }
So we know that short circuiting doesn't help because all booleans are true. And the instantiation costs make it slower. The first case we gain 1.1s and the second case we lost 1.2s. So I am on the boarder line of whether or not to do short circuiting on these constructor constraints. I think in the real case, it is likely that those booleans are all true because if the user calls these converting constructors, it is likely to succeed instead of falling back to the constructor that takes U&&. That means in real user code, the short circuiting might never happen. Comment Actions Thank you for looking into this so thoroughly. I should have better specified that my concerns were with meta-programming in static_asserts and other places where all of the conditions were likely to get evaluated. For constraints, I think the opposite is true, and lazy metaprogramming is the way to go.
Comment Actions I'm still going to request changes because of the no_unique_address thing, but this basically LGTM. Thanks a lot for the great patch and the great collaboration on this review. I'm extremely happy with the patch and in particular with the fact that we carefully considered the testing coverage during our review of the class itself (e.g. order of operations in assignment, etc.).
Comment Actions CI
Comment Actions address remaining comments
Comment Actions @huixie90 Thank you so much for this great patch. It's not an easy one but I'm extremely happy with the state it has reached now, and I think we've addressed everyone's comments. I'm excited to ship this in LLVM 16! I still left a few comments for minor nits, but feel free to ship this once they are addressed. Thank you, and thanks to other folks who chimed in on the review!
|
This is not what we usually do for exception classes -- we put their vtable in the dylib via a key function to avoid getting one vtable per TU and having the dynamic linker do a bunch of work. And then we use availability attributes to catch misuse at compile-time.
That being said, the way this has been designed (by using a class template below) means that we'll already have a profusion of these vtables in TUs, and the dynamic linker will already have a bunch of work to do. So I'm not sure it is worth hiding the <void> specialization in the dylib, given that it adds deployment target restrictions.
So I'd leave it as-is, but I think the rationale above is important to keep in mind.