I'm way out of my depth in the variant discussion. Seems to me it revolves around all the trade offs regarding design choices. Could we perhaps decide not to decide. Suppose we create a type which looks like: template< bool BasicGuaranteeSupported, bool StrongGuaranteeSupported, bool EmptyStateProhibited, bool AssignmentSupported, bool MoveSupported, class T ...
struct mother_of_variants{ // some trivial mp11 code }; Now we could define: using boost_variant = mother_of_all variants< bool true, //BasicGuaranteeSupported, bool true, //StrongGuaranteeSupported, bool true, //EmptyStateProhibited, bool true, //AssignmentSupported, bool true, //MoveSupported,
;
std_variant, std_optional, std_expect, boost_outcome, etc. would be similarly defined. And some "extras" like no_except_variant, trivial_variant, etc. would be added. and of course my favorite candidate: using safe_variant = mother_of_all variants< bool true, //BasicGuaranteeSupported, bool true, //StrongGuaranteeSupported, bool true, //EmptyStateProhibited, bool false, //AssignmentSupported, bool false //MoveSupported,
;
In addition, we'd have some type traits: mother_of_all_variants::no_runtime_allocation<mov>; mother_of_all_variants::no_double_instancen<mov>; mother_of_all_variants::is_mother_of_all_variants<mov>; etc. Just think that of all the time we've spent on discussions about optional, expected, std::variant, boost::variant, etc. At this rate, these discussions will keep hounding us forever. Of course I realize that the main purpose of having these variants of variants is to justify the discussions - but perhaps we might be ready for a new topic. Robert Ramey
On 16.04.19 03:51, Robert Ramey via Boost wrote:
I'm way out of my depth in the variant discussion. Seems to me it revolves around all the trade offs regarding design choices. Could we perhaps decide not to decide. Suppose we create a type which looks like:
template< bool BasicGuaranteeSupported, bool StrongGuaranteeSupported, bool EmptyStateProhibited, bool AssignmentSupported, bool MoveSupported, class T ...
struct mother_of_variants{ // some trivial mp11 code };
I did something similar back when I did type_safe::variant: You have a `VariantPolicy` that controls whether the variant can be empty and a has a `change_value()` function that switches the type in whatever way it seems fit. With that I have a `rarely_empty_variant` that is like `std::variant`, a `never_empty_variant` that assumes move is no-throw (and calls terminate() if they throw) and an `optional_variant` that fully embraces an empty state. At the time I thought it was a good trade-off, but haven't re-evaluated it since then.
On 4/16/19 4:28 AM, Jonathan Müller via Boost wrote:
On 16.04.19 03:51, Robert Ramey via Boost wrote:
I'm way out of my depth in the variant discussion. Seems to me it revolves around all the trade offs regarding design choices. Could we perhaps decide not to decide. Suppose we create a type which looks like:
template< bool BasicGuaranteeSupported, bool StrongGuaranteeSupported, bool EmptyStateProhibited, bool AssignmentSupported, bool MoveSupported, class T ... > struct mother_of_variants{ // some trivial mp11 code };
I did something similar back when I did type_safe::variant: You have a `VariantPolicy` that controls whether the variant can be empty and a has a `change_value()` function that switches the type in whatever way it seems fit.
With that I have a `rarely_empty_variant` that is like `std::variant`, a `never_empty_variant` that assumes move is no-throw (and calls terminate() if they throw) and an `optional_variant` that fully embraces an empty state.
At the time I thought it was a good trade-off, but haven't re-evaluated it since then.
I like this; however, years ago there was some argument about how providing such a policy for smart pointers would be confusing to users. However, I didn't really agree with that argument and the alternative is, AFAICT, not much better: std::variant, or boost::variant2 or libx::variant3 or ...
On 4/16/19 5:41 AM, Larry Evans via Boost wrote:
On 4/16/19 4:28 AM, Jonathan Müller via Boost wrote:
On 16.04.19 03:51, Robert Ramey via Boost wrote:
I'm way out of my depth in the variant discussion. Seems to me it revolves around all the trade offs regarding design choices. Could we perhaps decide not to decide. Suppose we create a type which looks like:
template< bool BasicGuaranteeSupported, bool StrongGuaranteeSupported, bool EmptyStateProhibited, bool AssignmentSupported, bool MoveSupported, class T ... > struct mother_of_variants{ // some trivial mp11 code };
I did something similar back when I did type_safe::variant: You have a `VariantPolicy` that controls whether the variant can be empty and a has a `change_value()` function that switches the type in whatever way it seems fit.
With that I have a `rarely_empty_variant` that is like `std::variant`, a `never_empty_variant` that assumes move is no-throw (and calls terminate() if they throw) and an `optional_variant` that fully embraces an empty state.
At the time I thought it was a good trade-off, but haven't re-evaluated it since then.
I like this; however, years ago there was some argument about how providing such a policy for smart pointers would be confusing to users. However, I didn't really agree with that argument and the alternative is, AFAICT, not much better: std::variant, or boost::variant2 or libx::variant3 or ...
Right. We could have it both ways. That is the mother of all variants could be used to generate a custom type given the requirements. For those who don't want/need to delve into all the details, we provide a list of specialization for commonly used variants which would probably address 99.9% of users needs. Robert Ramey
Are there real-world examples of well-designed classes who's move assignment operators throw? I think if I saw that in a code review I'd be inclined to demand a different design choice. Is there a good reason that variants should support such classes? On Tue, 16 Apr 2019 at 17:42, Robert Ramey via Boost <boost@lists.boost.org> wrote:
On 4/16/19 5:41 AM, Larry Evans via Boost wrote:
On 4/16/19 4:28 AM, Jonathan Müller via Boost wrote:
On 16.04.19 03:51, Robert Ramey via Boost wrote:
I'm way out of my depth in the variant discussion. Seems to me it revolves around all the trade offs regarding design choices. Could we perhaps decide not to decide. Suppose we create a type which looks like:
template< bool BasicGuaranteeSupported, bool StrongGuaranteeSupported, bool EmptyStateProhibited, bool AssignmentSupported, bool MoveSupported, class T ...
struct mother_of_variants{ // some trivial mp11 code };
I did something similar back when I did type_safe::variant: You have a `VariantPolicy` that controls whether the variant can be empty and a has a `change_value()` function that switches the type in whatever way it seems fit.
With that I have a `rarely_empty_variant` that is like `std::variant`, a `never_empty_variant` that assumes move is no-throw (and calls terminate() if they throw) and an `optional_variant` that fully embraces an empty state.
At the time I thought it was a good trade-off, but haven't re-evaluated it since then.
I like this; however, years ago there was some argument about how providing such a policy for smart pointers would be confusing to users. However, I didn't really agree with that argument and the alternative is, AFAICT, not much better: std::variant, or boost::variant2 or libx::variant3 or ...
Right. We could have it both ways. That is the mother of all variants could be used to generate a custom type given the requirements. For those who don't want/need to delve into all the details, we provide a list of specialization for commonly used variants which would probably address 99.9% of users needs.
Robert Ramey
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Richard Hodges hodges.r@gmail.com office: +442032898513 home: +376841522 mobile: +376380212
wt., 16 kwi 2019 o 23:57 Richard Hodges via Boost <boost@lists.boost.org> napisał(a):
Are there real-world examples of well-designed classes who's move assignment operators throw?
I think if I saw that in a code review I'd be inclined to demand a different design choice.
Is there a good reason that variants should support such classes?
Some implementations of std::list throw from their move constructors and default constructors. I understand that the implementation is easier if even an empty list has a dummy node, which requires allocation. Regards, &rzej;
On Tue, 16 Apr 2019 at 17:42, Robert Ramey via Boost < boost@lists.boost.org> wrote:
On 4/16/19 5:41 AM, Larry Evans via Boost wrote:
On 4/16/19 4:28 AM, Jonathan Müller via Boost wrote:
On 16.04.19 03:51, Robert Ramey via Boost wrote:
I'm way out of my depth in the variant discussion. Seems to me it revolves around all the trade offs regarding design choices. Could we perhaps decide not to decide. Suppose we create a type which looks like:
template< bool BasicGuaranteeSupported, bool StrongGuaranteeSupported, bool EmptyStateProhibited, bool AssignmentSupported, bool MoveSupported, class T ...
struct mother_of_variants{ // some trivial mp11 code };
I did something similar back when I did type_safe::variant: You have a `VariantPolicy` that controls whether the variant can be empty and a has a `change_value()` function that switches the type in whatever way it seems fit.
With that I have a `rarely_empty_variant` that is like `std::variant`, a `never_empty_variant` that assumes move is no-throw (and calls terminate() if they throw) and an `optional_variant` that fully embraces an empty state.
At the time I thought it was a good trade-off, but haven't re-evaluated it since then.
I like this; however, years ago there was some argument about how providing such a policy for smart pointers would be confusing to users. However, I didn't really agree with that argument and the alternative is, AFAICT, not much better: std::variant, or boost::variant2 or libx::variant3 or ...
Right. We could have it both ways. That is the mother of all variants could be used to generate a custom type given the requirements. For those who don't want/need to delve into all the details, we provide a list of specialization for commonly used variants which would probably address 99.9% of users needs.
Robert Ramey
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Richard Hodges hodges.r@gmail.com office: +442032898513 home: +376841522 mobile: +376380212
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On Wed, 17 Apr 2019 at 14:42, Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
wt., 16 kwi 2019 o 23:57 Richard Hodges via Boost <boost@lists.boost.org> napisał(a):
Are there real-world examples of well-designed classes who's move assignment operators throw?
I think if I saw that in a code review I'd be inclined to demand a different design choice.
Is there a good reason that variants should support such classes?
Some implementations of std::list throw from their move constructors and default constructors. I understand that the implementation is easier if even an empty list has a dummy node, which requires allocation.
I don't wish to sound too challenging but it seems to me that it would better for the community is the maintainers of these rogue list types got their act together. People who are looking for the easy way out have no business maintaining the fundamental building blocks of our entire universe. In de-facto reality, I would be stunned to find code that expects and gracefully manages an exception during a move construction. I would put a lot of money behind a bet that says anything you find in production today will either `abort` or head off into UB land if this happens. If anyone has designed a class to throw from its move constructor, they need to be sent for immediate re-education. It seems to me that making variants (and indeed everything!) demand nothrow moves will make them safer, because rogue programs will observably and predictably crash if the unthinkable should happen.
Regards, &rzej;
On Tue, 16 Apr 2019 at 17:42, Robert Ramey via Boost < boost@lists.boost.org> wrote:
On 4/16/19 5:41 AM, Larry Evans via Boost wrote:
On 4/16/19 4:28 AM, Jonathan Müller via Boost wrote:
On 16.04.19 03:51, Robert Ramey via Boost wrote:
I'm way out of my depth in the variant discussion. Seems to me it revolves around all the trade offs regarding design choices. Could we perhaps decide not to decide. Suppose we create a type which looks like:
template< bool BasicGuaranteeSupported, bool StrongGuaranteeSupported, bool EmptyStateProhibited, bool AssignmentSupported, bool MoveSupported, class T ... > struct mother_of_variants{ // some trivial mp11 code };
I did something similar back when I did type_safe::variant: You
have a
`VariantPolicy` that controls whether the variant can be empty and a has a `change_value()` function that switches the type in whatever way it seems fit.
With that I have a `rarely_empty_variant` that is like `std::variant`, a `never_empty_variant` that assumes move is no-throw (and calls terminate() if they throw) and an `optional_variant` that fully embraces an empty state.
At the time I thought it was a good trade-off, but haven't re-evaluated it since then.
I like this; however, years ago there was some argument about how providing such a policy for smart pointers would be confusing to users. However, I didn't really agree with that argument and the alternative is, AFAICT, not much better: std::variant, or boost::variant2 or libx::variant3 or ...
Right. We could have it both ways. That is the mother of all variants could be used to generate a custom type given the requirements. For those who don't want/need to delve into all the details, we provide a list of specialization for commonly used variants which would probably address 99.9% of users needs.
Robert Ramey
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Richard Hodges hodges.r@gmail.com office: +442032898513 home: +376841522 mobile: +376380212
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Richard Hodges hodges.r@gmail.com office: +442032898513 home: +376841522 mobile: +376380212
Dear Boost, One of the arguments against policy types stemmed from the subtle issue with things like `char_traits` and `Allocator` being template arguments: they spawn new types which may not be interopable. There is also the argument that "vocabulary types should be singular, concrete types". One of the great strengths and principals of C++ is having *decent* performance by default and choosing a default that most people can interop with (e.g., it is the safest or the most common vocabulary spelling) while letting users who care (which tend to be a lot of people in their own application-specific trenches) pick what makes sense for them. As it stands, we probably should have standardized a basic_variant<Policy, ...> and basic_optional<Policy, ....> before defining hard structs which inherit all member functions (struct variant: basic_variant<rarely_empty, ...> { /* */ }; and friends) to create sensible defaults for the community. At the cost of more up-front work, this would have saved us a great deal of effort and time in this domain by giving a decent default to interop with between libraries while giving the people with concerns for reaching the local maxima of their (private) ecosystems the chance to do so without rolling variant2 or optional.v3 or Yet Another Expected/Outcome Type. As a library developer, it's hard to not have just The One Variant Type because it makes writing APIs a bit difficult if you can't template on Policy. On the other hand, it's infinitely frustrating as both a library developer and an application developer when there's only one type and I only need to tweak some small bit of its behavior but instead have to roll an entirely new type and share absolutely no functionality with the original, let alone have the ability to create a reasonable constructor-based interop story. I think in the future we will start settling closer and closer to having customizable types where it makes sense. There's too much good design and performance and memory being dropped on the floor otherwise. Sincerely, JeanHeyd Meneide
On Thu, May 30, 2019 at 9:17 AM JeanHeyd Meneide via Boost < boost@lists.boost.org> wrote:
As it stands, we probably should have standardized a basic_variant<Policy, ...> and basic_optional<Policy, ....> before defining hard structs which inherit all member functions (struct variant: basic_variant<rarely_empty, ...> { /* */ }; and friends) to create sensible defaults for the community.
Ugh, no. Things that behave differently should be named differently, and policies, tags, etc., should only be used for things that we can't name differently for other reasons (such as tags passed to constructors).
At the cost of more up-front work, this would have saved us a great deal of effort and time in this domain by giving a decent default to interop with between libraries while giving the people with concerns for reaching the local maxima of their (private) ecosystems the chance to do so without rolling variant2 or optional.v3 or Yet Another Expected/Outcome Type.
Except that you have no idea what operations are guaranteed to be usable generically, and the moment you provide those guarantees (say by documentation), you cannot make any behavioral changes because you'll break the generic code. Take string for example: there are plenty of string classes out there which aren't specializations of basic_string, nor should they be. I much prefer the string_view approach, as that gives you the common operations w/o a tight coupling to some templated string class.
On the other hand, it's infinitely frustrating as both a library developer and an application developer when there's only one type and I only need to tweak some small bit of its behavior but instead have to roll an entirely new type and share absolutely no functionality with the original, let alone have the ability to create a reasonable constructor-based interop story.
basic_* doesn't solve that problem, because generic code may be dependent on that small bit of behavior. -- Nevin ":-)" Liber <mailto:nevin@cplusplusguy.com <nevin@eviloverlord.com>> +1-847-691-1404
On Thu, May 30, 2019 at 1:48 PM Nevin Liber via Boost <boost@lists.boost.org> wrote:
On Thu, May 30, 2019 at 9:17 AM JeanHeyd Meneide via Boost < boost@lists.boost.org> wrote:
As it stands, we probably should have standardized a basic_variant<Policy, ...> and basic_optional<Policy, ....> before defining hard structs which inherit all member functions (struct variant: basic_variant<rarely_empty, ...> { /* */ }; and friends) to create sensible defaults for the community.
Ugh, no.
Things that behave differently should be named differently, and policies, tags, etc., should only be used for things that we can't name differently for other reasons (such as tags passed to constructors).
At the cost of more up-front work, this would have saved us a great deal of effort and time in this domain by giving a decent default to interop with between libraries while giving the people with concerns for reaching the local maxima of their (private) ecosystems the chance to do so without rolling variant2 or optional.v3 or Yet Another Expected/Outcome Type.
Except that you have no idea what operations are guaranteed to be usable generically, and the moment you provide those guarantees (say by documentation), you cannot make any behavioral changes because you'll break the generic code.
Take string for example: there are plenty of string classes out there which aren't specializations of basic_string, nor should they be.
I much prefer the string_view approach, as that gives you the common operations w/o a tight coupling to some templated string class.
On the other hand, it's infinitely frustrating as both a library developer and an application developer when there's only one type and I only need to tweak some small bit of its behavior but instead have to roll an entirely new type and share absolutely no functionality with the original, let alone have the ability to create a reasonable constructor-based interop story.
basic_* doesn't solve that problem, because generic code may be dependent on that small bit of behavior. -- Nevin ":-)" Liber <mailto:nevin@cplusplusguy.com <nevin@eviloverlord.com>>
Exactly. - The point of vocabulary types is to be a type that can be passed around. - Same name == same behaviour because templates run on names, and expect same behaviour (as do users) Alexandrescu had a policy based smart pointer. Boost didn't, and it was good. Tony
On Wed, Apr 17, 2019 at 7:42 AM Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
Some implementations of std::list throw from their move constructors and default constructors. I understand that the implementation is easier if even an empty list has a dummy node, which requires allocation.
While the authors can certainly chime in (some lurk here sometimes) I have never heard them make the argument that it is about ease of implementation. If the nodes are in the heap, end iterators are preserved when swapping or moving (as they are for vector). I also vaguely remember something about fancy pointers and sentinel nodes, but I don't know fancy pointers. -- Nevin ":-)" Liber <mailto:nevin@cplusplusguy.com <nevin@eviloverlord.com>> +1-847-691-1404
On Wed, Apr 17, 2019 at 8:42 AM Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
wt., 16 kwi 2019 o 23:57 Richard Hodges via Boost <boost@lists.boost.org> napisał(a):
Are there real-world examples of well-designed classes who's move assignment operators throw?
I think if I saw that in a code review I'd be inclined to demand a different design choice.
Is there a good reason that variants should support such classes?
Some implementations of std::list throw from their move constructors and default constructors. I understand that the implementation is easier if even an empty list has a dummy node, which requires allocation.
Also classes that use the PIMPL idiom.
On 6/26/19 7:03 PM, David Sankel via Boost wrote:
On Wed, Apr 17, 2019 at 8:42 AM Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
wt., 16 kwi 2019 o 23:57 Richard Hodges via Boost <boost@lists.boost.org> napisał(a):
Are there real-world examples of well-designed classes who's move assignment operators throw?
From time to time I think about this. Is swap guarenteed not to throw? Or is this the same question. The real question is what should mother of variants policy look like and what traits on the types need to be supported. The more I think about this and see it come up again, again and again, I'm thinking that it was a mistake not to pursue this from the beginning. It's the only way the discussion can ever be resolved. I still undecided on whether the catchy name was the best choice. On one hand - it's catchy and descriptive and memorable. It also provokes curiosity on the part of those who might not otherwise look at it. On the other hand, it suggests that it's a joke - which admitadly was how it started, but now it's turned into something serious. Sort of like my whole life I guess. I'm sort of surprised that no one has taken of the challenge of making all current variants std, boost, ... obsolete. Where are the young people here? Robert Ramey
On Thu, 27 Jun 2019 at 09:35, Robert Ramey via Boost <boost@lists.boost.org> wrote:
From time to time I think about this. Is swap guarenteed not to throw?
As from C++11 std::swap is marked noexcept, so yes. As from C++20 it's also constexpr, so now it can definitely cannot throw. degski -- @realdegski https://edition.cnn.com/interactive/2019/06/middleeast/saudi-teen-death-pena... "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding
Am 27.06.19 um 08:45 schrieb degski via Boost:
As from C++11 std::swap is marked noexcept, so yes. As from C++20 it's also constexpr, so now it can definitely cannot throw.
degski Are you sure? According to https://en.cppreference.com/w/cpp/algorithm/swap this is only non-throwing if it can be moved without throwing
On Thu, 27 Jun 2019 at 09:54, Alexander Grund via Boost < boost@lists.boost.org> wrote:
Am 27.06.19 um 08:45 schrieb degski via Boost:
As from C++11 std::swap is marked noexcept, so yes. As from C++20 it's also constexpr, so now it can definitely cannot throw.
degski Are you sure? According to https://en.cppreference.com/w/cpp/algorithm/swap this is only non-throwing if it can be moved without throwing
Yes, but std::swap must also not invalidate any references, pointers, or iterators referring to the elements of the containers being swapped, which leaves the scope for throwing rather small [if at all possible]. degski -- @realdegski https://edition.cnn.com/interactive/2019/06/middleeast/saudi-teen-death-pena... "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding
Am 27.06.19 um 09:06 schrieb degski via Boost:
On Thu, 27 Jun 2019 at 09:54, Alexander Grund via Boost < boost@lists.boost.org> wrote:
Am 27.06.19 um 08:45 schrieb degski via Boost:
As from C++11 std::swap is marked noexcept, so yes. As from C++20 it's also constexpr, so now it can definitely cannot throw.
degski Are you sure? According to https://en.cppreference.com/w/cpp/algorithm/swap this is only non-throwing if it can be moved without throwing
Yes, but std::swap must also not invalidate any references, pointers, or iterators referring to the elements of the containers being swapped, which leaves the scope for throwing rather small [if at all possible].
degski
Combining it with "Some implementations of std::list throw from their move constructors" (David Sankel) leads to `std::swap(list1, list2)` can throw and hence is not `noexcept(true)` This applies to any "strange" class whose move may throw which was the motivation of that (part of the) discussion. So answering
Is there a good reason that variants should support such classes?
Maybe. At least it is also supported by std::move
On 27/06/2019 19:06, degski wrote:
Yes, but std::swap must also not invalidate any references, pointers, or iterators referring to the elements of the containers being swapped, which leaves the scope for throwing rather small [if at all possible].
Are you sure about that? Perhaps it is a failure of my imagination (or I'm misunderstanding what you're saying), but that doesn't even seem possible.
On Thu, 27 Jun 2019 at 10:33, Gavin Lambert via Boost <boost@lists.boost.org> wrote:
On 27/06/2019 19:06, degski wrote:
Yes, but std::swap must also not invalidate any references, pointers, or iterators referring to the elements of the containers being swapped, which leaves the scope for throwing rather small [if at all possible].
Are you sure about that?
Perhaps it is a failure of my imagination (or I'm misunderstanding what you're saying), but that doesn't even seem possible.
I'm just reading (https://en.cppreference.com/w/cpp/container/list/list): "After container move construction (overload (6)), references, pointers, and iterators (other than the end iterator) to other remain valid, but refer to elements that are now in *this. The current standard makes this guarantee via the blanket statement in §23.2.1[container.requirements.general]/12, and a more direct guarantee is under consideration via LWG 2321 <http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#2321>." Possibly I'm reading it wrongly, please set me right in that case. LWG 2321 <http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#2321> goes on about std::swap as well. degski -- @realdegski https://edition.cnn.com/interactive/2019/06/middleeast/saudi-teen-death-pena... "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding
On 27/06/2019 19:40, degski wrote:
I'm just reading (https://en.cppreference.com/w/cpp/container/list/list): "After container move construction (overload (6)), references, pointers, and iterators (other than the end iterator) to other remain valid, but refer to elements that are now in *this. The current standard makes this guarantee via the blanket statement in §23.2.1[container.requirements.general]/12, and a more direct guarantee is under consideration via LWG 2321 <http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#2321>."
Possibly I'm reading it wrongly, please set me right in that case. LWG 2321 <http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#2321> goes on about std::swap as well.
Hmm. I read that the same way too. I wonder how that works with checked/debug/safe iterators that contain a reference to the container that they were from -- presumably after a swap they're now pointing at the second container's elements, but still have a reference to the first container? Unless it can somehow swap what container all the iterators point at too, without touching each one individually.
Am 27.06.19 um 09:33 schrieb Gavin Lambert via Boost:
On 27/06/2019 19:06, degski wrote:
Yes, but std::swap must also not invalidate any references, pointers, or iterators referring to the elements of the containers being swapped, which leaves the scope for throwing rather small [if at all possible].
Are you sure about that?
Perhaps it is a failure of my imagination (or I'm misunderstanding what you're saying), but that doesn't even seem possible.
On how this is possible: Imagine `std::list` as a simple linked list (pointer to (element, pointer-to-next)-tuples). On move, you simply move the HEAD pointer to the new list and a NULL to the old list. Result: Pointers/References to all elements are still valid. Same for e.g. vectors: Only pointers to array are swapped -> Unchanged element pointers obviously after `swap(foo, bar)` an old pointer `oldFoo = &foo[1]` set before the swap does now point into `bar`: `assert(oldFoo == &bar[1])`
On Thu, 27 Jun 2019 at 11:01, Alexander Grund via Boost < boost@lists.boost.org> wrote:
Am 27.06.19 um 09:33 schrieb Gavin Lambert via Boost:
On 27/06/2019 19:06, degski wrote:
Yes, but std::swap must also not invalidate any references, pointers, or iterators referring to the elements of the containers being swapped, which leaves the scope for throwing rather small [if at all possible].
Are you sure about that?
Perhaps it is a failure of my imagination (or I'm misunderstanding what you're saying), but that doesn't even seem possible.
On how this is possible: Imagine `std::list` as a simple linked list (pointer to (element, pointer-to-next)-tuples). On move, you simply move the HEAD pointer to the new list and a NULL to the old list.
Result: Pointers/References to all elements are still valid. Same for e.g. vectors: Only pointers to array are swapped -> Unchanged element pointers
How does that gel with: "Some implementations of std::list throw from their move constructors" (David Sankel) ..." [because I would think the same as you]? Yes, swap head around, and if you really want safety, you can use a xor-swap of the [those] pointer[s], i.e. guaranteed no allocation (stack or heap). degski -- @realdegski https://edition.cnn.com/interactive/2019/06/middleeast/saudi-teen-death-pena... "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding
Am 27.06.19 um 10:13 schrieb degski:
On Thu, 27 Jun 2019 at 11:01, Alexander Grund via Boost <boost@lists.boost.org <mailto:boost@lists.boost.org>> wrote:
On how this is possible: Imagine `std::list` as a simple linked list (pointer to (element, pointer-to-next)-tuples). On move, you simply move the HEAD pointer to the new list and a NULL to the old list.
Result: Pointers/References to all elements are still valid. Same for e.g. vectors: Only pointers to array are swapped -> Unchanged element pointers
How does that gel with: "Some implementations of std::list throw from their move constructors" (David Sankel) ..." [because I would think the same as you]? Yes, swap head around, and if you really want safety, you can use a xor-swap of the [those] pointer[s], i.e. guaranteed no allocation (stack or heap).
He also wrote: "I understand that the implementation is easier if even an empty list has a dummy node, which requires allocation." So a move constructor must construct a dummy node for the moved-from list and that allocation might throw. I don't know WHY one would require this, but I'll just take his word that it MAY be required.
On Thu, 27 Jun 2019 at 11:24, Alexander Grund <alexander.grund@tu-dresden.de> wrote:
He also wrote: "I understand that the implementation is easier if even an empty list has a dummy node, which requires allocation."
I don't know how common this is, but it sounds like the std::list from hell. This means that default construction allocates as well I presume [there goes my, "I did not want to pay for that", no "we took the easy road, pay now and here"]. degski -- @realdegski https://edition.cnn.com/interactive/2019/06/middleeast/saudi-teen-death-pena... "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding
On 6/26/19 11:45 PM, degski via Boost wrote:
On Thu, 27 Jun 2019 at 09:35, Robert Ramey via Boost <boost@lists.boost.org> wrote:
From time to time I think about this. Is swap guarenteed not to throw?
As from C++11 std::swap is marked noexcept, so yes. As from C++20 it's also constexpr, so now it can definitely cannot throw.
Hmmm - my understanding of noexcept was not that it couldn't throw but than any attempt to throw would result in immediate termination rather than an exception. (I'm confused about noexcept as well). In anycase I should have phrased the above as: Is swap guaranteed to succeed? Robert Ramey
On 6/27/19 6:16 PM, Robert Ramey via Boost wrote:
On 6/26/19 11:45 PM, degski via Boost wrote:
On Thu, 27 Jun 2019 at 09:35, Robert Ramey via Boost <boost@lists.boost.org> wrote:
From time to time I think about this. Is swap guarenteed not to throw?
As from C++11 std::swap is marked noexcept, so yes. As from C++20 it's also constexpr, so now it can definitely cannot throw.
No, std::swap is marked conditionally noexcept if is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>. Though usually this condition is true.
Hmmm - my understanding of noexcept was not that it couldn't throw but than any attempt to throw would result in immediate termination rather than an exception. (I'm confused about noexcept as well).
Both statements are true: - From the noexcept function perspective, if an exception tries to propagate out of the function scope, call std::terminate(). - From the caller's perspective, a noexcept function will never throw.
In anycase I should have phrased the above as:
Is swap guaranteed to succeed?
In general, no. It is only guaranteed to succeed if it is noexcept. See above when it is noexcept. In C++17 there is is_nothrow_swappable trait that allows to test this.
On 6/27/19 8:43 AM, Andrey Semashev via Boost wrote: - From the caller's perspective, a noexcept function will never throw.
In anycase I should have phrased the above as:
Is swap guaranteed to succeed?
In general, no. It is only guaranteed to succeed if it is noexcept. See above when it is noexcept.
that allows to test this.
A... - I'm still confused. How can one say that swap is guaranteed to succeed when it can result in calling terminate()? In C++17 there is is_nothrow_swappbale trait this leads to useful information
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On 6/27/19 7:32 PM, Robert Ramey via Boost wrote:
On 6/27/19 8:43 AM, Andrey Semashev via Boost wrote: - From the caller's perspective, a noexcept function will never throw.
In anycase I should have phrased the above as:
Is swap guaranteed to succeed?
In general, no. It is only guaranteed to succeed if it is noexcept. See above when it is noexcept.
A... - I'm still confused. How can one say that swap is guaranteed to succeed when it can result in calling terminate()?
First, people normally don't write noexcept functions knowing they might throw. Meaning that the call to terminate() is equivalent to a bug. Second, your code after a noexcept swap() returns will not be run if terminate() gets called. Meaning that the code is guaranteed to only operate when swap() successfully returned.
On 6/27/19 9:47 AM, Andrey Semashev via Boost wrote:
On 6/27/19 7:32 PM, Robert Ramey via Boost wrote:
On 6/27/19 8:43 AM, Andrey Semashev via Boost wrote: - From the caller's perspective, a noexcept function will never throw.
In anycase I should have phrased the above as:
Is swap guaranteed to succeed?
In general, no. It is only guaranteed to succeed if it is noexcept. See above when it is noexcept.
A... - I'm still confused. How can one say that swap is guaranteed to succeed when it can result in calling terminate()?
First, people normally don't write noexcept functions knowing they might throw.
Right - but swap is a templated function. The obvious way to implement swap is via moves on type T. So how could swap be guaranteed to succeed for any T? Meaning that the call to terminate() is equivalent to a bug. LOL - right - I'm trying to figure out how to guarantee that my program doesn't have a bug.
Second, your code after a noexcept swap() returns will not be run if terminate() gets called.
Meaning that the code is guaranteed to only operate when swap() successfully returned. LOL - I get this. The question is: how can one know that swap will be successfully returned? for a given type T.
In other words, how can it make sense that swap<T> be noexcept without considering the specific type T? Robert Ramey
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On 6/27/19 8:12 PM, Robert Ramey via Boost wrote:
On 6/27/19 9:47 AM, Andrey Semashev via Boost wrote:
First, people normally don't write noexcept functions knowing they might throw.
Right - but swap is a templated function. The obvious way to implement swap is via moves on type T. So how could swap be guaranteed to succeed for any T?
It is not guaranteed for any T. As I wrote earlier, std::swap will only be noexcept if the corresponding functions of T are noexcept. A custom overload of swap for some type T may or may not be noexcept on its own terms, of course.
LOL - I get this. The question is: how can one know that swap will be successfully returned? for a given type T.
Obviously, there's no such trait as is_buggy_move_operators<T>. But bugs aside, this is done by testing is_nothrow_swappable_v<T>. Or, before C++17, with a construction similar to this: template< typename T > T& make_lvalue() noexcept; constexpr bool is_nothrow_swappable = noexcept(swap(make_lvalue<T>(), make_lvalue<T>()));
In other words, how can it make sense that swap<T> be noexcept without considering the specific type T?
You can't say swap() is noexcept without considering T.
But bugs aside, this is done by testing is_nothrow_swappable_v<T>. Or, before C++17, with a construction similar to this:
template< typename T > T& make_lvalue() noexcept;
constexpr bool is_nothrow_swappable = noexcept(swap(make_lvalue<T>(), make_lvalue<T>()));
Outcome did this in v2.1.0. It didn't work right, swap() wasn't always instanceable at the point of parsing basic_result. I replaced it with an exact replica of std::is_nothrow_swappable, and now it works properly in v2.1.1.
In other words, how can it make sense that swap<T> be noexcept without considering the specific type T?
Robert, you may find studying Outcome's strong_swap() of interest. Ref page: https://ned14.github.io/outcome/reference/functions/strong_swap/ Implementation: https://github.com/ned14/outcome/blob/develop/include/outcome/detail/value_s... tldr; implementing the strong guarantee is much harder than it looks, and actually offers surprisingly little benefit in practice. It's also a pain to implement without making constexpr puke, at least in C++ 14. All in all, the weak guarantee is likely plenty good for almost everybody almost all of the time. Niall
On 6/27/19 1:53 PM, Niall Douglas via Boost wrote:
Robert, you may find studying Outcome's strong_swap() of interest.
I certainly would if I were going to implement the mother of all variants.
Ref page: https://ned14.github.io/outcome/reference/functions/strong_swap/
Implementation: https://github.com/ned14/outcome/blob/develop/include/outcome/detail/value_s...
tldr; implementing the strong guarantee is much harder than it looks, and actually offers surprisingly little benefit in practice. It's also a pain to implement without making constexpr puke, at least in C++ 14. All in all, the weak guarantee is likely plenty good for almost everybody almost all of the time.
Here you've exactly hit on the motivation for mother of all variants. It should be clear by now that as library developers we cannot correctly anticipate the needs and desires of our potential users and at the same time document the rational and restrictions of the particular variant in question. Ramey's law. When something is really hard to do - do something else. That something else would be to a) familiarize oneself with all the discussions of the different kinds of variants b) synthesize these discussions to a template meta programming class policy and related english document. This class would implement the mapping "set of properties" => variant class. If a given set of properties could not produce such a class it would be a compile time error. c) make a couple of examples and tests d) make aliases for outcome, expected, boost variant, std variant boost variant2, std optional, boost optional. e) each of the above aliases should be a one statement template instantiation of the above tmp policy class. f) If e) is not possible, you're not done, go bact to b above. that is what I'm suggesting is possible and that some ambitious person undertake to accomplish.
Niall
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On Thu, Jun 27, 2019 at 7:41 PM Robert Ramey via Boost < boost@lists.boost.org> wrote:
Here you've exactly hit on the motivation for mother of all variants.
It should be clear by now that as library developers we cannot correctly anticipate the needs and desires of our potential users and at the same time document the rational and restrictions of the particular variant in question.
That is true about every type in existence. Variant is not special in this regard. And if "we cannot correctly anticipate the needs and desires of our potential users", policies also "cannot correctly anticipate the needs and desires of our potential users" either. Policies do not solve this problem. So, why conflate all these different behaviors into one template type? Normally, it is to handle things generically, but if they have different behaviors, you cannot handle them generically. It's the vector<bool> problem all over again.
that is what I'm suggesting is possible and that some ambitious person undertake to accomplish.
As always in any volunteer organization, if you are the one who thinks it is important, you are the one that needs to start doing the work. -- Nevin ":-)" Liber <mailto:nevin@cplusplusguy.com <nevin@eviloverlord.com>> +1-847-691-1404
On Tue, Jul 2, 2019 at 7:19 PM Nevin Liber via Boost <boost@lists.boost.org> wrote:
On Thu, Jun 27, 2019 at 7:41 PM Robert Ramey via Boost < boost@lists.boost.org> wrote:
Here you've exactly hit on the motivation for mother of all variants.
It should be clear by now that as library developers we cannot correctly anticipate the needs and desires of our potential users and at the same time document the rational and restrictions of the particular variant in question.
That is true about every type in existence. Variant is not special in
this
regard.
And if "we cannot correctly anticipate the needs and desires of our potential users", policies also "cannot correctly anticipate the needs and desires of our potential users" either. Policies do not solve this problem.
+1 Worse, policy-based designs are the result of the expert in the problem domain (the library author), unable to make up his mind about the library design, pushing that responsibility to people who are less knowledgeable (the library users).
On 7/3/19 3:36 PM, Emil Dotchevski via Boost wrote:
On Tue, Jul 2, 2019 at 7:19 PM Nevin Liber via Boost <boost@lists.boost.org> wrote:
On Thu, Jun 27, 2019 at 7:41 PM Robert Ramey via Boost < boost@lists.boost.org> wrote:
Here you've exactly hit on the motivation for mother of all variants.
It should be clear by now that as library developers we cannot correctly anticipate the needs and desires of our potential users and at the same time document the rational and restrictions of the particular variant in question.
That is true about every type in existence. Variant is not special in
this
regard.
And if "we cannot correctly anticipate the needs and desires of our potential users", policies also "cannot correctly anticipate the needs and desires of our potential users" either. Policies do not solve this problem.
+1
Worse, policy-based designs are the result of the expert in the problem domain (the library author), unable to make up his mind about the library design, pushing that responsibility to people who are less knowledgeable (the library users).
I wouldn't put it that far. A policy can be useful when there are legitimate use cases for multiple behaviors in some aspect for the otherwise same component.
On Wed, Jul 3, 2019 at 3:41 PM Andrey Semashev via Boost < boost@lists.boost.org> wrote:
On 7/3/19 3:36 PM, Emil Dotchevski via Boost wrote:
On Tue, Jul 2, 2019 at 7:19 PM Nevin Liber via Boost <
wrote:
On Thu, Jun 27, 2019 at 7:41 PM Robert Ramey via Boost < boost@lists.boost.org> wrote:
Here you've exactly hit on the motivation for mother of all variants.
It should be clear by now that as library developers we cannot
anticipate the needs and desires of our potential users and at the same time document the rational and restrictions of the particular variant in question.
That is true about every type in existence. Variant is not special in
correctly this
regard.
And if "we cannot correctly anticipate the needs and desires of our potential users", policies also "cannot correctly anticipate the needs and desires of our potential users" either. Policies do not solve this problem.
+1
Worse, policy-based designs are the result of the expert in the problem domain (the library author), unable to make up his mind about the
boost@lists.boost.org> library
design, pushing that responsibility to people who are less knowledgeable (the library users).
I wouldn't put it that far. A policy can be useful when there are legitimate use cases for multiple behaviors in some aspect for the otherwise same component.
A policy is an example of a customization point. Obviously, many customization points are legit yet, practically speaking, policy-based designs tend to be like I described them.
On 7/3/19 5:36 AM, Emil Dotchevski via Boost wrote:
On Tue, Jul 2, 2019 at 7:19 PM Nevin Liber via Boost <boost@lists.boost.org>
And if "we cannot correctly anticipate the needs and desires of our potential users", policies also "cannot correctly anticipate the needs and desires of our potential users" either. Policies do not solve this problem.
+1
Worse, policy-based designs are the result of the expert in the problem domain (the library author), unable to make up his mind about the library design,
The are the result of the fact that, depending on the usage of the type, different design feature should be selected. pushing that responsibility to people (users) Right who are less knowledgeable Not necessarily - and highly suspect.
(the library users).
The policy based design is an implement technique to specify the set of legitimate variations of a design which the user might need without the need to write multiple types. Right now we have: boost::variant std::variant boost::variant2 boost::outcome ?::expected boost::optional std::optional ... ? Each one of these is a hodgepodge of design decisions arrived at after very, very, very long speculative discussion. That are really all just special cases of the the concept of "typesafe variant". Navigating and documenting the differences is a very time consuming endeavor for anyone. It is not an efficient way to do things.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On 7/3/19 7:42 PM, Robert Ramey via Boost wrote:
The policy based design is an implement technique to specify the set of legitimate variations of a design which the user might need without the need to write multiple types. Right now we have:
boost::variant std::variant boost::variant2 boost::outcome ?::expected boost::optional std::optional ... ?
Each one of these is a hodgepodge of design decisions arrived at after very, very, very long speculative discussion. That are really all just special cases of the the concept of "typesafe variant".
We've already had this discussion, and I'll reiterate that these types are not merely special cases - they serve their specific purposes and have interfaces and behavior tailored for their respective uses. You could just as well say that every C++ type is a special case of a typesafe variant - with only one alternative. This is as true as it is useless.
On Wed, Jul 3, 2019 at 3:40 PM Andrey Semashev via Boost <boost@lists.boost.org> wrote:
On 7/3/19 7:42 PM, Robert Ramey via Boost wrote:
Each one of these is a hodgepodge of design decisions arrived at after very, very, very long speculative discussion. That are really all just special cases of the the concept of "typesafe variant".
We've already had this discussion, and I'll reiterate that these types are not merely special cases - they serve their specific purposes and have interfaces and behavior tailored for their respective uses.
You could just as well say that every C++ type is a special case of a typesafe variant - with only one alternative. This is as true as it is useless.
This is a straw man argument, you're refuting a gross exaggeration of your opponent's position. -- Frank
On 7/4/19 1:37 AM, Frank Mori Hess wrote:
On Wed, Jul 3, 2019 at 3:40 PM Andrey Semashev via Boost <boost@lists.boost.org> wrote:
On 7/3/19 7:42 PM, Robert Ramey via Boost wrote:
Each one of these is a hodgepodge of design decisions arrived at after very, very, very long speculative discussion. That are really all just special cases of the the concept of "typesafe variant".
We've already had this discussion, and I'll reiterate that these types are not merely special cases - they serve their specific purposes and have interfaces and behavior tailored for their respective uses.
You could just as well say that every C++ type is a special case of a typesafe variant - with only one alternative. This is as true as it is useless.
This is a straw man argument, you're refuting a gross exaggeration of your opponent's position.
Not at all. The types mentioned in Robert's message are very different, so merging them into one component makes as much sense to me as the speculation I made.
On Thu, Jul 4, 2019 at 10:49 AM Andrey Semashev via Boost < boost@lists.boost.org> wrote:
On 7/4/19 1:37 AM, Frank Mori Hess wrote:
On Wed, Jul 3, 2019 at 3:40 PM Andrey Semashev via Boost <boost@lists.boost.org> wrote:
On 7/3/19 7:42 PM, Robert Ramey via Boost wrote:
Each one of these is a hodgepodge of design decisions arrived at after very, very, very long speculative discussion. That are really all
just
special cases of the the concept of "typesafe variant".
We've already had this discussion, and I'll reiterate that these types are not merely special cases - they serve their specific purposes and have interfaces and behavior tailored for their respective uses.
You could just as well say that every C++ type is a special case of a typesafe variant - with only one alternative. This is as true as it is useless.
This is a straw man argument, you're refuting a gross exaggeration of your opponent's position.
Not at all. The types mentioned in Robert's message are very different, so merging them into one component makes as much sense to me as the speculation I made.
+1 Just because two types are similar syntactically, doesn't mean that they have similar semantics. For example, just because one needs variant's functionality to implement outcome, doesn't mean that semantically outcome should be e.g. an instance of some variant template. The whole point of correct design is to remove flexibility that does not pertain to what the interface is designed to do, leaving the user with only the options that the interface designer has determined to be necessary and sufficient.
On 5/07/2019 00:08, Emil Dotchevski wrote:
Not at all. The types mentioned in Robert's message are very different, so merging them into one component makes as much sense to me as the speculation I made.
+1
Just because two types are similar syntactically, doesn't mean that they have similar semantics. For example, just because one needs variant's functionality to implement outcome, doesn't mean that semantically outcome should be e.g. an instance of some variant template.
The whole point of correct design is to remove flexibility that does not pertain to what the interface is designed to do, leaving the user with only the options that the interface designer has determined to be necessary and sufficient.
I agree, but that just suggests that the "mother of all variants" should actually be a "variant_storage" type; you can then have "variant" and "optional" etc expose only parts of its interface in their own interface, either through standard composition or through private inheritance.
On 7/4/19 6:34 PM, Gavin Lambert via Boost wrote:
On 5/07/2019 00:08, Emil Dotchevski wrote:
Not at all. The types mentioned in Robert's message are very different, so merging them into one component makes as much sense to me as the speculation I made.
+1
Just because two types are similar syntactically, doesn't mean that they have similar semantics. For example, just because one needs variant's functionality to implement outcome, doesn't mean that semantically outcome should be e.g. an instance of some variant template.
The whole point of correct design is to remove flexibility that does not pertain to what the interface is designed to do, leaving the user with only the options that the interface designer has determined to be necessary and sufficient.
I agree, but that just suggests that the "mother of all variants" should actually be a "variant_storage" type; you can then have "variant" and "optional" etc expose only parts of its interface in their own interface, either through standard composition or through private inheritance.
+1 This makes the most sense to me. You might also parameterize the type of the index. For example, for optional, it would be a boolean type. For std::variant, it might be unsigned long. Then, like Gavin since, the interface would just be an adaptor over this "variant_storage" type. -Larry
On 7/4/19 1:49 AM, Andrey Semashev via Boost wrote: boost::variant std::variant boost::variant2 boost::outcome ?::expected boost::optional std::optional ... ?
Not at all. The types mentioned in Robert's message are very different,
Ahhh - perhaps this is the crux of the issue. I look at the following and see them as very similar rather than very different. So naturally I see them as just specialized versions of a more general thing. To one who disagrees with this view, my proposal will lack merit. I think we can agree on this. Robert Ramey
On 7/3/19 12:40 PM, Andrey Semashev via Boost wrote:
On 7/3/19 7:42 PM, Robert Ramey via Boost wrote:
The policy based design is an implement technique to specify the set of legitimate variations of a design which the user might need without the need to write multiple types. Right now we have:
boost::variant std::variant boost::variant2 boost::outcome ?::expected boost::optional std::optional ... ?
Each one of these is a hodgepodge of design decisions arrived at after very, very, very long speculative discussion. That are really all just special cases of the the concept of "typesafe variant".
We've already had this discussion, and I'll reiterate that these types are not merely special cases - they serve their specific purposes and have interfaces and behavior tailored for their respective uses.
To me, it's just about code reuse. The class with the policies contains all the variations for different features. The aliases just select which features are to be used. Now, it's hard to know whether or not this would result of a simplification or more complexity without actually undertaking the work. This would have to start with reviewing all the variant types above and the documentation and likely the commentary about them. It's a big job. Until that happens, whether or not it's an effective idea or not is just speculation.
On 7/4/19 8:00 AM, Robert Ramey via Boost wrote:
On 7/3/19 12:40 PM, Andrey Semashev via Boost wrote:
On 7/3/19 7:42 PM, Robert Ramey via Boost wrote:
The policy based design is an implement technique to specify the set of legitimate variations of a design which the user might need without the need to write multiple types. Right now we have:
boost::variant std::variant boost::variant2 boost::outcome ?::expected boost::optional std::optional ... ?
Each one of these is a hodgepodge of design decisions arrived at after very, very, very long speculative discussion. That are really all just special cases of the the concept of "typesafe variant".
We've already had this discussion, and I'll reiterate that these types are not merely special cases - they serve their specific purposes and have interfaces and behavior tailored for their respective uses.
To me, it's just about code reuse. The class with the policies contains all the variations for different features. The aliases just select which features are to be used.
Code can be reused without implementing this meta-variant.
Am 27.06.19 um 04:03 schrieb David Sankel via Boost:
On Wed, Apr 17, 2019 at 8:42 AM Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
wt., 16 kwi 2019 o 23:57 Richard Hodges via Boost <boost@lists.boost.org> napisał(a):
Are there real-world examples of well-designed classes who's move assignment operators throw?
I think if I saw that in a code review I'd be inclined to demand a different design choice.
Is there a good reason that variants should support such classes?
Some implementations of std::list throw from their move constructors and default constructors. I understand that the implementation is easier if even an empty list has a dummy node, which requires allocation.
Also classes that use the PIMPL idiom.
If a class uses PIMPL and does a move, isn't that just a pointer swap which cannot throw? Or would that be implemented as another allocation in the moved-from class? I can't imagine why.
On Thu, Jun 27, 2019 at 2:11 AM Alexander Grund via Boost < boost@lists.boost.org> wrote:
Am 27.06.19 um 04:03 schrieb David Sankel via Boost:
On Wed, Apr 17, 2019 at 8:42 AM Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
wt., 16 kwi 2019 o 23:57 Richard Hodges via Boost < boost@lists.boost.org> napisał(a):
Are there real-world examples of well-designed classes who's move assignment operators throw?
I think if I saw that in a code review I'd be inclined to demand a different design choice.
Is there a good reason that variants should support such classes?
Some implementations of std::list throw from their move constructors and default constructors. I understand that the implementation is easier if even an empty list has a dummy node, which requires allocation.
Also classes that use the PIMPL idiom.
If a class uses PIMPL and does a move, isn't that just a pointer swap which cannot throw? Or would that be implemented as another allocation in the moved-from class? I can't imagine why.
Move assignment is fine. Move construction is where allocation would need to happen so the "moved from" object stays in a valid state.
On 28/06/2019 07:57, David Sankel wrote:
If a class uses PIMPL and does a move, isn't that just a pointer swap which cannot throw? Or would that be implemented as another allocation in the moved-from class? I can't imagine why.
Move assignment is fine. Move construction is where allocation would need to happen so the "moved from" object stays in a valid state.
I'm not sure why. Both unique_ptr and shared_ptr (one of which is usually used as the "guts" of pimpl, depending on whether you want it to be reference-semantics-copyable or not) do not need to allocate on move construction (and are noexcept) -- they just leave the moved-from object empty.
On Thu, Jun 27, 2019, 7:09 PM Gavin Lambert via Boost <boost@lists.boost.org> wrote:
On 28/06/2019 07:57, David Sankel wrote:
If a class uses PIMPL and does a move, isn't that just a pointer swap which cannot throw? Or would that be implemented as another allocation in the moved-from class? I can't imagine why.
Move assignment is fine. Move construction is where allocation would need to happen so the "moved from" object stays in a valid state.
I'm not sure why. Both unique_ptr and shared_ptr (one of which is usually used as the "guts" of pimpl, depending on whether you want it to be reference-semantics-copyable or not) do not need to allocate on move construction (and are noexcept) -- they just leave the moved-from object empty.
Many would prefer their PIMPL classes to not have an artificial empty/partially formed state because of the increased semantic complexity that implies.
On 28/06/2019 16:12, David Sankel wrote:
Many would prefer their PIMPL classes to not have an artificial empty/partially formed state because of the increased semantic complexity that implies.
Maybe, but that seems a bit daft to me. The whole point of move construction and assignment is to steal things from an object that's about to be thrown away -- spending time and resources to allocate more memory for it just to preserve a "never empty" state is counterproductive in that scenario. Granted that in the case where someone misbehaves and does try to use an invalid operation on an empty object (ie. anything other than assignment, destruction, or empty()/explicit bool/whatever), people like neither the wide interface of empty checks in each public method nor the implementation-defined-behaviour of accessing a null pointer -- but at least on most platforms the latter is reasonably guaranteed to crash immediately rather than having any other adverse effect, so it seems like the lesser of all evils to me. Just accept that it can be empty, and that your program will crash if you do something inappropriate with an empty object. No reason to introduce a "never empty" invariant where it will harm performance.
On Fri, 28 Jun 2019 at 07:41, Gavin Lambert via Boost <boost@lists.boost.org> wrote:
Just accept that it can be empty, and that your program will crash if you do something inappropriate with an empty object. No reason to introduce a "never empty" invariant where it will harm performance.
Yes, bad code -> bad result, so what? degski -- @realdegski https://edition.cnn.com/interactive/2019/06/middleeast/saudi-teen-death-pena... "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding
On 28/06/2019 16:12, David Sankel wrote:
Many would prefer their PIMPL classes to not have an artificial empty/partially formed state because of the increased semantic complexity that implies.
Maybe, but that seems a bit daft to me. Agreed. The increased complexity doesn't exist if you consider a moved-from object as invalid. You can either have a raw nullptr or an empty unique_ptr or an unchanged shared_ptr. Neither of them will even add a single additional check to the code base. At the very maximum you need 1 more check in the destructor if your PIMPL is some kind of interface handle-like which cannot be freed if it is "null" (whatever
Am 28.06.19 um 06:40 schrieb Gavin Lambert via Boost: this means for that). Example would be INVALID_HANDLE for files and such.
On Fri, Jun 28, 2019 at 2:44 AM Alexander Grund via Boost < boost@lists.boost.org> wrote:
On 28/06/2019 16:12, David Sankel wrote:
Many would prefer their PIMPL classes to not have an artificial empty/partially formed state because of the increased semantic complexity that implies.
Maybe, but that seems a bit daft to me. Agreed. The increased complexity doesn't exist if you consider a moved-from object as invalid. You can either have a raw nullptr or an empty unique_ptr or an unchanged shared_ptr. Neither of them will even add a single additional check to the code base. At the very maximum you need 1 more check in the destructor if your PIMPL is some kind of interface handle-like which cannot be freed if it is "null" (whatever
Am 28.06.19 um 06:40 schrieb Gavin Lambert via Boost: this means for that). Example would be INVALID_HANDLE for files and such.
I get this, but the whole motivation for the "never empty guarantee" in variant types equally applies to PIMPL types. The approach of embracing partially formed data structures is consistent and has performance benefits, but the benefits of not having these states shouldn't be ignored either IMHO.
On Tue, Apr 16, 2019 at 5:57 PM Richard Hodges via Boost <boost@lists.boost.org> wrote:
Are there real-world examples of well-designed classes who's move assignment operators throw?
I think if I saw that in a code review I'd be inclined to demand a different design choice.
Is there a good reason that variants should support such classes?
I'm going to say *most* classes have a throwing move operators. Why? Because most classes have a custom copy constructor (because people don't follow Rule of Zero like they should), and thus most classes don't have *any* move operations. So all the move operations are actually copy operations, and most of those classes have a string or vector or whatever, and can throw on copy. Now, having said that, none of those classes actually throw in "real life", because they only throw when memory is completely exhausted, and have probably crashed already. And on some systems that oversubscribe allocation, they don't throw at all, they just crash. Thus most move operators, whether they exist or not, don't really throw.
On 5/30/19 4:17 PM, Gottlob Frege via Boost wrote:
I'm going to say *most* classes have a throwing move operators. Why? Because most classes have a custom copy constructor (because people don't follow Rule of Zero like they should), and thus most classes don't have *any* move operations.
So all the move operations are actually copy operations, and most of those classes have a string or vector or whatever, and can throw on copy.
Now, having said that, none of those classes actually throw in "real life", because they only throw when memory is completely exhausted, and have probably crashed already. And on some systems that oversubscribe allocation, they don't throw at all, they just crash.
Thus most move operators, whether they exist or not, don't really throw.
This rather illustrates my point. As it stands now we've got std::variant, boost::variant and boost::variant2. And while we're at it don't for get std::optional, boost::optional, boost::outcome, std::expected? and ...? I don't think there's an general expectation that any of these are going to interoperate - but they might by accident. So I need one of these - or maybe something very similar. As a user I'm now tasked with a mini-research project to figure out which one I want. OK, not too bad. But also which might be incompatible with the types T I'm going to want to use. Or which might have a surprising throw when I use them. One approach is yours, carefully design the particular type of variant with the features that we expect users will require. This will never result in agreement and some compromise will have to be reached. And this compromise will surely contain subtle points that the user will need to research and understand in order to code which is clearly correct. So I'm all for giving up. Encapsulate the debates/tradoffs into the list of policy parameters and in type requirements for the type. So effectively we'll have: template< variant_requirements_on_T<bool is_assignable, iscopyable, is_moveable, is_no_throw, is_default_constructible, ...>, // list of restrictions of behavior of the variant being created class T ... // list of types in the union
struct mother_of_all_variants { ... }; This type would trap at compile time any attempts instantiate at type which violates the specified requirements. This is what the user actually needs in order to write a program that he can have confidence in. And of course we'd have template<class T> using optional = mother_all_variants< ..., bool> to avoid the necessity to do any actual thinking at all. Aside - I'm not crazy about the naming "basic_variant" as I don't think it does justice to the brilliance of the concept. But I recognize that such naming is much in line with historical practice and users expectations so I see the merit in "basic_variant". Aside2 - can one imagine the satisfaction to be derived from knowing that forever more, we won't be subject to any more arcane debates on what behavior some variant class should support? Robert Ramey
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On 31. May 2019, at 01:50, Robert Ramey via Boost <boost@lists.boost.org> wrote:
On 5/30/19 4:17 PM, Gottlob Frege via Boost wrote:
I'm going to say *most* classes have a throwing move operators. Why? Because most classes have a custom copy constructor (because people don't follow Rule of Zero like they should), and thus most classes don't have *any* move operations. So all the move operations are actually copy operations, and most of those classes have a string or vector or whatever, and can throw on copy. Now, having said that, none of those classes actually throw in "real life", because they only throw when memory is completely exhausted, and have probably crashed already. And on some systems that oversubscribe allocation, they don't throw at all, they just crash. Thus most move operators, whether they exist or not, don't really throw.
This rather illustrates my point. As it stands now we've got std::variant, boost::variant and boost::variant2. And while we're at it don't for get std::optional, boost::optional, boost::outcome, std::expected? and ...?
I don't think there's an general expectation that any of these are going to interoperate - but they might by accident.
Peter followed the spec for std::variant, so boost::variant2 is a drop-in replacement for std::variant. I think one should not use boost::variant anymore in new projects anymore. So the choice is really between std::variant (you need a C++17 compiler) and boost::variant2 (a C++11 compiler is sufficient), which are interchangeable. Hans
On 16. Apr 2019, at 17:42, Robert Ramey via Boost <boost@lists.boost.org> wrote:
Right. We could have it both ways. That is the mother of all variants could be used to generate a custom type given the requirements. For those who don't want/need to delve into all the details, we provide a list of specialization for commonly used variants which would probably address 99.9% of users needs.
I haven't participated in the discussion so far, but I think this is the best solution, considering the length of the debate. Since the solutions have different trade-offs and there is no clear way to argue for one particular trade-off, then it would be the best thing to provide a variant that allows you to chose your trade-off. The library designer can still set the default to whatever he/she feels most comfortable with. It is true that this potentially requires a lot of additional code to maintain. I nevertheless think that a new boost::variant would have more impact if it could be a catch-all for all the design choices that are floating around on github. There are quite many variants out there. The purpose of boost, in my view, is to provide a standard solution so that people can stop reinventing the wheel. We already have the std::variant in C++17, which chose a particular trade-off. I don't think it is very helpful if boost just adds another variant with one particular trade-off. Boost libraries tend to give the developer more choice, and I value that. For example, for the boost::container::vector, I can set the growth factor if I want to. That's great and not provided by the std::vector. For now, boost::variant2 also has the advantage that it is C++11 instead of C++17, but that advantage will wear off very quickly as time passes and more C++17 capable compilers are used in the field. If boost::variant2 was a mother_of_all_variants (please do not call it really like that), it can have a lasting impact on the C++ community even then. Best regards, Hans
participants (17)
-
Alexander Grund
-
Andrey Semashev
-
Andrzej Krzemienski
-
David Sankel
-
degski
-
Emil Dotchevski
-
Frank Mori Hess
-
Gavin Lambert
-
Gottlob Frege
-
Hans Dembinski
-
JeanHeyd Meneide
-
Jonathan Müller
-
Larry Evans
-
Nevin Liber
-
Niall Douglas
-
Richard Hodges
-
Robert Ramey