[variant2] Review of Variant2 started today : April 1 - April 10
The Boost formal review of Peter Dimov's Variant2 library will take place April 1 - 10, 2019. Please consider participating in this review. The success of Boost is partially a result of the quality review process which is conducted by the community. You are part of the Boost community. I will be grateful to receive a review based on whatever level of effort or time you can devote. Variant2 is a never-valueless C++11/14/17 implementation of std::variant.
From the README:
The class boost::variant2::variant
On 4/2/19 3:55 PM, Peter Dimov via Boost wrote:
Michael Caisse wrote:
Note: The repository contains an expected implementation also; however, that is not being considered in this review.
To be clear, I do intend to finish `expected` and provide it as part of the library.
Will it be reviewed separately? Also, IMHO, it's better to have libraries more focused and fine grained. Why not have `expected` as a separate library?
Andrey Semashev wrote:
On 4/2/19 3:55 PM, Peter Dimov via Boost wrote:
Michael Caisse wrote:
Note: The repository contains an expected implementation also; however, that is not being considered in this review.
To be clear, I do intend to finish `expected` and provide it as part of the library.
Will it be reviewed separately?
No. If you don't like it, now is the time to reject.
Also, IMHO, it's better to have libraries more focused and fine grained. Why not have `expected` as a separate library?
expected
On 4/2/19 4:18 PM, Peter Dimov via Boost wrote:
Andrey Semashev wrote:
On 4/2/19 3:55 PM, Peter Dimov via Boost wrote:
Michael Caisse wrote:
Note: The repository contains an expected implementation also; however, >> that is not being considered in this review.
To be clear, I do intend to finish `expected` and provide it as part of > the library.
Will it be reviewed separately?
No. If you don't like it, now is the time to reject.
In that case, `expected` should be part of this review and contribute to the final decision.
Also, IMHO, it's better to have libraries more focused and fine grained. Why not have `expected` as a separate library?
expected
is basically a variant, with a slightly different interface.
It might be implemented like/with variant, but conceptually this is a different component. Just like `optional` is a different component than `variant`. All these components have different use cases and communicate different intentions.
On 4/2/19 7:31 AM, Andrey Semashev via Boost wrote:
On 4/2/19 4:18 PM, Peter Dimov via Boost wrote:
Andrey Semashev wrote:
Michael Caisse wrote:
Note: The repository contains an expected implementation also; however, >> that is not being considered in this review.
To be clear, I do intend to finish `expected` and provide it as
On 4/2/19 3:55 PM, Peter Dimov via Boost wrote: part of > the library.
Will it be reviewed separately?
No. If you don't like it, now is the time to reject.
In that case, `expected` should be part of this review and contribute to the final decision.
Also, IMHO, it's better to have libraries more focused and fine grained. Why not have `expected` as a separate library?
expected
is basically a variant, with a slightly different interface. It might be implemented like/with variant, but conceptually this is a different component. Just like `optional` is a different component than `variant`. All these components have different use cases and communicate different intentions.
Personally, I've always found that characterizing option, expected, et. all as different/unique components is a bad idea. I much prefer a single simple idea - a variant as a simple sum of other types - while the others are more specific "variations" of the fundamental type. It's less redundant, easier to reason about, easier to remember, and less work to document. There is also a natural C++ implementation in terms of inheritance. Robert Ramey
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrey Semashev wrote:
Also, IMHO, it's better to have libraries more focused and fine grained. Why not have `expected` as a separate library?
expected
is basically a variant, with a slightly different interface. It might be implemented like/with variant, but conceptually this is a different component. Just like `optional` is a different component than `variant`. All these components have different use cases and communicate different intentions.
It is a different component. That wasn't the question though. The question was, why not have it as a separate library. And the answer is, because the two components share too much of an implementation, and because the design decisions made about one component affect the other (and vice versa - the converting constructor and the subset() member function of `variant` are needed by `expected`).
wt., 2 kwi 2019 o 16:44 Peter Dimov via Boost
Andrey Semashev wrote:
Also, IMHO, it's better to have libraries more focused and fine grained. Why not have `expected` as a separate library?
expected
is basically a variant, with a slightly different interface. It might be implemented like/with variant, but conceptually this is a different component. Just like `optional` is a different component than `variant`. All these components have different use cases and communicate different intentions.
It is a different component. That wasn't the question though. The question was, why not have it as a separate library. And the answer is, because the two components share too much of an implementation, and because the design decisions made about one component affect the other (and vice versa - the converting constructor and the subset() member function of `variant` are needed by `expected`).
This sounds a bit like variant2 is just a byproduct from the implementation of `expected`. Regards, &rzej;
On Tue, Apr 2, 2019 at 12:31 AM Michael Caisse via Boost < boost@lists.boost.org> wrote:
The Boost formal review of Peter Dimov's Variant2 library will take place April 1 - 10, 2019.
[snip] Are there any numbers regarding compile- and runtime perf comparisons vs. boost::variant and std::variant(s)? Zach
Zach Laine wrote:
Are there any numbers regarding compile- and runtime perf comparisons vs. boost::variant and std::variant(s)?
Yes, see https://github.com/pdimov/variant2/tree/develop/benchmark
On Tue, Apr 2, 2019 at 11:37 AM Peter Dimov via Boost
Zach Laine wrote:
Are there any numbers regarding compile- and runtime perf comparisons vs. boost::variant and std::variant(s)?
Yes, see https://github.com/pdimov/variant2/tree/develop/benchmark
Thanks. Zach
The following is my attempt to give a high-level answer to the questions
raised so far.
* First, the submission is a variant type that provides the basic exception
safety guarantee and the "never-empty" guarantee. "Never empty", in this
context, has the specific meaning that variant
pt., 5 kwi 2019 o 02:00 Peter Dimov via Boost
The following is my attempt to give a high-level answer to the questions raised so far.
* First, the submission is a variant type that provides the basic exception safety guarantee and the "never-empty" guarantee. "Never empty", in this context, has the specific meaning that variant
has an invariant that it always contains a valid object of one of the given types T1, T2, ..., Tn, and is not allowed any other state. A possibly-empty variant can be well approximated by adding `monostate` as one of the alternatives. This is recognized by `variant` as the empty state, and is preferentially used as a fallback on exception. If monostate is the first alternative, as is the common case, variant will default-construct to it.
Does this happen *only* when `monostate` is at index 0, or when `monostate` is at any index? I thought from the sources that `monostate` on any index gives the guarantee.
The variant design space is fairly well known, and has been sufficiently explored. Possibly-empty variants (a perfectly legitimate design option) exist. Here is one: http://eggs-cpp.github.io/variant/index.html
However, the type under review is what it is. I understand that everybody has his own preference on design matters, but I've made my choice, and this is what is being reviewed.
* Second, the submission contains a variant-like type `expected
`, which at this point does not meet the review criteria of having a test suite, and a finished documentation. (The unfinished documentation is at https://github.com/pdimov/variant2/blob/develop/doc/expected.md.) I decided to request the review at this time because variant<> is ready for use, and providing it in Boost seemed more practical (people could take advantage of it) than blocking it until I find the time to finish `expected`. And I didn't remove `expected` from the submission in order to allow reviewers to take a look at it and express a preference, if they so choose, on what they feel its fate ought to be.
Traditionally, once libraries are accepted, we don't hold a review on each subsequent addition. But an advance warning couldn't hurt.
So, in your review, you can state what you want done with `expected`. If the submission is accepted in its entirety, I will finish the `expected` test suite and documentation before adding the library to Boost. Or, if the submission is accepted on the condition `expected` not be added without a review, I will remove it before adding the library, and not add it later without a review.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski wrote:
A possibly-empty variant can be well approximated by adding `monostate` as one of the alternatives. This is recognized by `variant` as the empty state, and is preferentially used as a fallback on exception. If monostate is the first alternative, as is the common case, variant will default-construct to it.
Does this happen *only* when `monostate` is at index 0, or when `monostate` is at any index? I thought from the sources that `monostate` on any index gives the guarantee.
Like std::variant, variant2 always default-constructs to the first
alternative. That is, the default constructor of variant
pt., 5 kwi 2019 o 19:04 Peter Dimov via Boost
Andrzej Krzemienski wrote:
A possibly-empty variant can be well approximated by adding `monostate` as one of the alternatives. This is recognized by `variant` as the empty state, and is preferentially used as a fallback on exception. If monostate is the first alternative, as is the common case, variant will default-construct to it.
Does this happen *only* when `monostate` is at index 0, or when `monostate` is at any index? I thought from the sources that `monostate` on any index gives the guarantee.
Like std::variant, variant2 always default-constructs to the first alternative. That is, the default constructor of variant
initializes a contained value of type T1.
Ah ok; default-constructs to `monostate` when at index 0, and falls back (after exception) to `monostate` if at any index.
I vote *ACCEPT*.
*Design*
I consider it a shortcoming that std::variant does not have the
never-valueless guarantee of variant2. The point of variant is that
after a successful initialization, it will either have a valid A or a valid
B. If I wanted to have to deal with some other alternative, I would have
specified a third type.
The main objection to the never-valueless semantics is that after a failed
assignment, the programmer could be surprised to find that the target
variant object may have switched from a valid value of the previously
stored type, to a valid value of one of the other alternative types. While
it is true that the change of type can be surprising, this is the good old
basic guarantee in action -- nothing less, nothing more.
I also like the addition of the narrowing operation subset
To never-empty or not to never-empty (a.k.a. Design): In general, since we already have std::variant with the valueless state, having an alternative that provides never empty guarantee is useful if only to gain practical experience with both approaches. Again, in general, I consider never empty guarantee a plus as it reduces the total number of states of a program, making it easier to reason about and removing performance overhead. However, I agree with Andrzej in that the behavior when after an exception a seemingly random value appears in the variant out of the thin air is not useful and may even be harmful. I realize that there are cases when one simply doesn't care about the variant contents after an exception, but still that kind of uncertainty seems unneeded and undesired to me. Frankly, in my experience, people (myself included) often simply forget or don't consider what will happen if assignment fails, leaving subtle bugs in the code. I would very much prefer to be explicit about when I allow a different value to appear in the variant. I think, including monostate in the list of types would be an adequate way of telling that intention. Meaning that when monostate is *not* present, I would not expect any funny business from the variant, and therefore the variant has to use double storage, if it must. It's disappointing that Boost.Variant2 does not depart from the existing Boost.Variant in this regard, which kind of diminishes its value. I'm not saying Boost.Variant design is bad, I'm saying that we already have it, and a potential new library could offer something different, something not supported by either std::variant or Boost.Variant, even if as an option. I guess, there is some value in facelifting Boost.Variant to the more recent C++ practices and interface closer to std::variant, which makes learning easier. Docs: - In general, the docs are fairly minimal. I think, it could benefit from more rationale discussion and guidelines for unexperienced users, like when to use std::variant, Boost.Variant and Boost.Variant2. I think, the very Boost.Variant2 (and the variant class itself) naming is unfortunate in this regard, as it could be a hint in itself. There's not a single usage example in the docs. I think, in the current form docs are sufficient for experienced programmers who have used some version of variant in the past, but not so much for new users who are picking their first variant ever. Some performance numbers would be useful as well, although not a requirement. - One important piece of information that is missing in the docs is what is the behavior if only the second bullet in the Overview is satisfied and assignment/emplace throws. Specifically, what is the resulting value of the variant in this case. - List of supported compilers and minimum C++ version is missing. - It would be nice to mention whether the double storage is static (i.e. it always contributes to the variant size) or dynamic. - In the synopsys, use std::size_t vs. size_t consistently. - In variant reference description, define that T0 is the first element of T argument pack. - The use of Boost.Mp11 in the deference description requires knowledge of Boost.Mp11, which is unfortunate. It's probably better to use standard C++ (e.g. C++17 folding expressions, even though the library itself might be C++11) or plain English. - I don't understand the description of the "variant(U&& u)" constructor. "build an imaginary function FUN(Ti) for each alternative type Ti" - what function, with what effect? Are you trying to say that the resulting type of the stored value is selected as-if by C++ overload resolution? I think, it should be phrased better. - In variant_alternative description, move the variant_alternative> specialization description to the beginning so that the other specializations on the various qualified Ts use the knowledge about what variant_alternative actually does and that T is supposed to be a variant. - In variant relational operators description, w.index - parenthesis missing. - Docs for expected are completely missing. API: - Why have valueless_by_exception() if it's never true? You're not giving a rationale in the docs. Trying to follow std::variant interface in this part is not a convincing argument because the never empty guarantee *is* the defining difference between the two. Someone who conciously chose variant2 will never want to check if it's valueless. - I think, a function indicating whether subset() is possible (i.e. the variant contains a value representable by the subset) would be useful. This would allow to avoid dealing with exception from subset(). - Moving subset() overloads should probably be noexcept(std::is_nothrow_move_constructible_v<T> &&...). - Given that there are no docs and next to no comments in the code, I'm not able to comment on expected. Implementation: I didn't pay much attention to the implementation, but I trust Peter's code is excellently written. Looking at expected implementation, it seems to be using variant through its public interfaces, so it should be possible to extract it as a separate library. My knowledge of the domain: I have experience using both Boost.Variant and std::variant. I'm not an often user of these components, though. My reviewing effort: A few hours reading the docs and glancing over the code. I didn't try compiling the code. Verdict: Given that I see Boost.Variant2 as a more modern equivalent to Boost.Variant, I would expect it to replace Boost.Variant over time. With that in mind, I vote to CONDITIONALLY ACCEPT the library, with the following conditions: - Significantly improve documentation. Specifically, provide sections discussing corner cases regarding state of the variant after exceptions, provide usage examples and a tutorial, provide design rationale. Ideally, add a migration guide from Boost.Variant. I'd like to see something closer to Boost.Variant docs. - Remove expected. It's not documented, it's not related to the variant implementation and it can totally be implemented as a separate library. Beside these mandatory requests, I'd be very interested if Peter presented an updated variant proposal, which doesn't create values out of thin air except if it's monostate. I know it's not what is being proposed now as Boost.Variant2, but had it been proposed, I would've been more interested in that proposal than this one. If Peter finds a way to incorporate this into Boost.Variant2 (e.g. as a policy or a separate variant type), I'd be happy as well. Lastly, thank you Peter for submitting this library for review and thank you Michael for managing the review (especially, for extending the review period).
Andrey Semashev wrote:
Beside these mandatory requests, I'd be very interested if Peter presented an updated variant proposal, which doesn't create values out of thin air except if it's monostate.
Since mostly everyone has objected to it - Bjorn has it as an acceptance condition - I'll remove the "default construct a suitable alternative" fallback. I'm not yet sure about the monostate special case, as it becomes even more special now. I tend towards removing that too, and just offering the strong guarantee.
On 4/14/19 7:05 PM, Peter Dimov via Boost wrote:
Andrey Semashev wrote:
Beside these mandatory requests, I'd be very interested if Peter presented an updated variant proposal, which doesn't create values out of thin air except if it's monostate.
Since mostly everyone has objected to it - Bjorn has it as an acceptance condition - I'll remove the "default construct a suitable alternative" fallback.
Glad to hear it, thanks!
I'm not yet sure about the monostate special case, as it becomes even more special now. I tend towards removing that too, and just offering the strong guarantee.
I think, monostate is useful - as I said in the review, it is a way to achieve single buffer when you explicitly accept it as a fallback. I'm in favor of keeping it.
AMDG On 4/14/19 10:05 AM, Peter Dimov via Boost wrote:
Andrey Semashev wrote:
Beside these mandatory requests, I'd be very interested if Peter presented an updated variant proposal, which doesn't create values out of thin air except if it's monostate.
Since mostly everyone has objected to it - Bjorn has it as an acceptance condition - I'll remove the "default construct a suitable alternative" fallback.
I'm not yet sure about the monostate special case, as it becomes even more special now. I tend towards removing that too, and just offering the strong guarantee.
What about this: Only consider default constructing the first type, not any type in the list. This should be much less surprising, because it creates the same state as a default-constructed variant. In Christ, Steven Watanabe
On 4/17/19 1:06 AM, Steven Watanabe via Boost wrote:
AMDG
On 4/14/19 10:05 AM, Peter Dimov via Boost wrote:
Andrey Semashev wrote:
Beside these mandatory requests, I'd be very interested if Peter presented an updated variant proposal, which doesn't create values out of thin air except if it's monostate.
Since mostly everyone has objected to it - Bjorn has it as an acceptance condition - I'll remove the "default construct a suitable alternative" fallback.
I'm not yet sure about the monostate special case, as it becomes even more special now. I tend towards removing that too, and just offering the strong guarantee.
What about this: Only consider default constructing the first type, not any type in the list. This should be much less surprising, because it creates the same state as a default-constructed variant.
I think, that's not much better than default-constructing a random value. There would be no way to prohibit such behavior. OTOH, monostate is a clear marker indicating that such behavior is allowed.
participants (8)
-
Andrey Semashev
-
Andrzej Krzemienski
-
Emil Dotchevski
-
Michael Caisse
-
Peter Dimov
-
Robert Ramey
-
Steven Watanabe
-
Zach Laine