Boost.Variant provides access through get() and visitors, but in C++11 we can do so much better! :) For a boost::variant<A,B,C>, the attached small utility function match() lets you write: match(v, [](A & a){ cout << "it's a A!"; } [](B & b){ cout << "oh, a B!"; } [](C & c){ cout << "ah yes, a C!"; }); Return values are supported. I think this really belongs in Boost. It makes variants much more usable. Regards, Eelis P.S. I have not attempted a C++98 implementation. P.S.2. Right after finishing my implementation, I discovered another, /vastly/ more complex implementation of the same thing: https://github.com/exclipy/inline_variant_visitor I'm not sure its complexity gives that implementation any advantages.
On January 7, 2015 3:41:04 AM EST, Eelis <eelis@eelis.net> wrote:
Boost.Variant provides access through get() and visitors, but in C++11 we can do so much better! :)
For a boost::variant<A,B,C>, the attached small utility function match() lets you write:
match(v, [](A & a){ cout << "it's a A!"; } [](B & b){ cout << "oh, a B!"; } [](C & c){ cout << "ah yes, a C!"; });
Return values are supported. I think this really belongs in Boost. It makes variants much more usable.
That looks nice, but I don't see how it will work for, say, variant<std::string,std::map<int,something>,int>. What's the common type for those types? ___ Rob (Sent from my portable computation engine)
On January 7, 2015 4:53:49 AM EST, Eelis <eelis@eelis.net> wrote:
On 2015-01-07 10:41, Rob Stewart wrote:
That looks nice, but I don't see how it will work for, say, variant<std::string,std::map<int,something>,int>. What's the common type for those types?
common_type is merely used for the return types of the functors.
Ah. I missed that. I was reading the code on my phone and it was rather jumbled. ___ Rob (Sent from my portable computation engine)
Le 07/01/15 09:41, Eelis a écrit :
Boost.Variant provides access through get() and visitors, but in C++11 we can do so much better! :)
For a boost::variant<A,B,C>, the attached small utility function match() lets you write:
match(v, [](A & a){ cout << "it's a A!"; } [](B & b){ cout << "oh, a B!"; } [](C & c){ cout << "ah yes, a C!"; });
Return values are supported. I think this really belongs in Boost. It makes variants much more usable.
Regards,
Eelis
P.S. I have not attempted a C++98 implementation.
P.S.2. Right after finishing my implementation, I discovered another, /vastly/ more complex implementation of the same thing:
https://github.com/exclipy/inline_variant_visitor
I'm not sure its complexity gives that implementation any advantages. Hi,
IIUC the code, your implementation has some limitations as the user must provide a function for each variant type and in the same order. There is another one here [1] based on the overload function as defined here [2]. There are some examples test here [3]. The same function can be used for optional/expected/any and many more sum types (even if any is not a sum type, the selection of some possible types makes it posible to see this selection as a sum type). Best, Vicente [1] https://github.com/viboes/tags/blob/master/include/yafpl/v1/functional/match... [2] https://github.com/viboes/tags/blob/master/include/yafpl/v1/functional/overl... [3] https://github.com/viboes/tags/blob/master/test/variant/include_pass.cpp
On Wed, Jan 7, 2015 at 12:41 AM, Eelis <eelis@eelis.net> wrote:
Boost.Variant provides access through get() and visitors, but in C++11 we can do so much better! :)
For a boost::variant<A,B,C>, the attached small utility function match() lets you write:
match(v, [](A & a){ cout << "it's a A!"; } [](B & b){ cout << "oh, a B!"; } [](C & c){ cout << "ah yes, a C!"; });
Return values are supported. I think this really belongs in Boost. It makes variants much more usable.
Regards,
Eelis
This has come up before in various contexts. I'm not a fan of "match" as it is implemented here for a few of reasons: * It does not allow one or more of the functions to work with multiple elements (I.E. if one of them is an lambda with an auto parameter). * It does not currently scale to n-ary visitation. * This implementation is linear and uses variant's get rather than simply re-using apply_visitor. * Directly common_type is probably not what you want here due to its behavior regarding references and because it is potentially order-dependent. My personal stance and I what I use on projects is something a little different and I try to evangelize: Make an overloads template that just works with apply_visitor or an apply_visitor like function: apply_visitor( overloads( [](A & a){ cout << "it's a A!"; } [](B & b){ cout << "oh, a B!"; } [](C & c){ cout << "ah yes, a C!"; } [](auto & d){ cout << "a default!"; } ), v ); All overloads does is make a visitor that contains an overload set of all of the passed-in function objects (done by base classes and bringing in the operator() functions from each base). Instead of this solution having its own dispatching mechanism it just works via apply_visitor so it has exactly the known behavior and works fine with n-ary visitation out of the box. Overload resolution is exactly C++ overload resolution in an overload set because each passing in function object's operator() is brought in through a using-declaration. Function objects that are function pointers are handled as well via internal wrapping. The main limitation of this approach is that overloads must copy/move the passed-in function objects. I.E. there is no known tie_overloads that would be able to exhibit the same behavior. -- -Matt Calabrese
Matt Calabrese wrote:
The main limitation of this approach is that overloads must copy/move the passed-in function objects. I.E. there is no known tie_overloads that would be able to exhibit the same behavior.
Hmm. If you had a reference_wrapper<F> which SFINAEd its operator() on whether F::operator() compiles, could you not then pack those reference wrappers into an overloads object? And on another note, even if we had the overloads() function template, it would still make sense to me to have something like match( ... ) that is just an alias for apply_visitor( overloads( ... ) ). I would even make it a member, in which case it would probably be more properly called 'apply'. :-)
On Wed, Jan 7, 2015 at 10:48 AM, Peter Dimov <lists@pdimov.com> wrote:
Matt Calabrese wrote:
The main limitation of this approach is that overloads must copy/move the passed-in function objects. I.E. there is no known tie_overloads that would be able to exhibit the same behavior.
Hmm. If you had a reference_wrapper<F> which SFINAEd its operator() on whether F::operator() compiles, could you not then pack those reference wrappers into an overloads object?
Unfortunately, no, because at that point you've "flattened" the operator() to having all template parameters. Overload resolution would no longer be able to produce better or worse matches when one or more of the passed-in function objects are callable. For instance, if you pass in tie_overloads( [](int a) {}, [](auto a) ) to apply_visitor, if the variant contained an "int" then the function call would actually be ambiguous rather than preferring the int overload since both overloads are callable and now just have template parameters as arguments. I've put a lot of thought (on/off for years) into trying to come up with a tie_overloads that actually works precisely as an overload set and I'm reasonably certain that it cannot be done, but I am unable to say that for certain. On the plus side, I've never actually found the lack of a tie_overloads to be a problem, since in times that I've personally wanted it it's been easy to manually make a reference-semantic function object at the call-site via lambdas. The main difficulty is that this just can't be done automatically inside of generic code, so the value-semantics of the function object passing sometimes bleeds out to the user a little bit in generic code (the user just needs to be aware the function objects are copied/moved in). In practice this isn't much of a problem since standard library algorithms take function objects by value anyway and so people are familiar with those semantics.
And on another note, even if we had the overloads() function template, it would still make sense to me to have something like match( ... ) that is just an alias for apply_visitor( overloads( ... ) ). I would even make it a member, in which case it would probably be more properly called 'apply'. :-)
True. There is also one thing that I do like about match -- it's possible to make one that requires an /exact/ match (I.E. in places where you want to handle each case individually and don't want overload resolution, more similar to a type switch). So both match (generalized for the n-ary case) and overloads still seem useful in different scenarios. I know another booster on this list has implemented this, though I don't think it's been posted. -- -Matt Calabrese
2015-01-08 3:14 GMT+08:00 Matt Calabrese <rivorus@gmail.com>:
On Wed, Jan 7, 2015 at 10:48 AM, Peter Dimov <lists@pdimov.com> wrote:
Matt Calabrese wrote:
The main limitation of this approach is that overloads must copy/move the passed-in function objects. I.E. there is no known tie_overloads that would be able to exhibit the same behavior.
Hmm. If you had a reference_wrapper<F> which SFINAEd its operator() on whether F::operator() compiles, could you not then pack those reference wrappers into an overloads object?
Unfortunately, no, because at that point you've "flattened" the operator() to having all template parameters. Overload resolution would no longer be able to produce better or worse matches when one or more of the passed-in function objects are callable. For instance, if you pass in tie_overloads( [](int a) {}, [](auto a) ) to apply_visitor, if the variant contained an "int" then the function call would actually be ambiguous rather than preferring the int overload since both overloads are callable and now just have template parameters as arguments. I've put a lot of thought (on/off for years) into trying to come up with a tie_overloads that actually works precisely as an overload set and I'm reasonably certain that it cannot be done, but I am unable to say that for certain.
On the plus side, I've never actually found the lack of a tie_overloads to be a problem, since in times that I've personally wanted it it's been easy to manually make a reference-semantic function object at the call-site via lambdas. The main difficulty is that this just can't be done automatically inside of generic code, so the value-semantics of the function object passing sometimes bleeds out to the user a little bit in generic code (the user just needs to be aware the function objects are copied/moved in). In practice this isn't much of a problem since standard library algorithms take function objects by value anyway and so people are familiar with those semantics.
At least, for functors that don't overload themselves (e.g. lambdas), you can do something like this: http://coliru.stacked-crooked.com/a/c38ed042f6b6b8a6 That said, I don't have such a need myself though, just for fun :p
2015-01-08 17:39 GMT+08:00 TONGARI J <tongari95@gmail.com>:
2015-01-08 3:14 GMT+08:00 Matt Calabrese <rivorus@gmail.com>:
On Wed, Jan 7, 2015 at 10:48 AM, Peter Dimov <lists@pdimov.com> wrote:
Matt Calabrese wrote:
The main limitation of this approach is that overloads must copy/move the passed-in function objects. I.E. there is no known tie_overloads that would be able to exhibit the same behavior.
Hmm. If you had a reference_wrapper<F> which SFINAEd its operator() on whether F::operator() compiles, could you not then pack those reference wrappers into an overloads object?
Unfortunately, no, because at that point you've "flattened" the operator() to having all template parameters. Overload resolution would no longer be able to produce better or worse matches when one or more of the passed-in function objects are callable. For instance, if you pass in tie_overloads( [](int a) {}, [](auto a) ) to apply_visitor, if the variant contained an "int" then the function call would actually be ambiguous rather than preferring the int overload since both overloads are callable and now just have template parameters as arguments. I've put a lot of thought (on/off for years) into trying to come up with a tie_overloads that actually works precisely as an overload set and I'm reasonably certain that it cannot be done, but I am unable to say that for certain.
On the plus side, I've never actually found the lack of a tie_overloads to be a problem, since in times that I've personally wanted it it's been easy to manually make a reference-semantic function object at the call-site via lambdas. The main difficulty is that this just can't be done automatically inside of generic code, so the value-semantics of the function object passing sometimes bleeds out to the user a little bit in generic code (the user just needs to be aware the function objects are copied/moved in). In practice this isn't much of a problem since standard library algorithms take function objects by value anyway and so people are familiar with those semantics.
At least, for functors that don't overload themselves (e.g. lambdas), you can do something like this: http://coliru.stacked-crooked.com/a/c38ed042f6b6b8a6
That said, I don't have such a need myself though, just for fun :p
Le 07/01/15 20:14, Matt Calabrese a écrit :
On Wed, Jan 7, 2015 at 10:48 AM, Peter Dimov <lists@pdimov.com> wrote:
Matt Calabrese wrote:
The main limitation of this approach is that overloads must copy/move the passed-in function objects. I.E. there is no known tie_overloads that would be able to exhibit the same behavior.
Hmm. If you had a reference_wrapper<F> which SFINAEd its operator() on whether F::operator() compiles, could you not then pack those reference wrappers into an overloads object?
Unfortunately, no, because at that point you've "flattened" the operator() to having all template parameters. Overload resolution would no longer be able to produce better or worse matches when one or more of the passed-in function objects are callable. For instance, if you pass in tie_overloads( [](int a) {}, [](auto a) ) to apply_visitor, if the variant contained an "int" then the function call would actually be ambiguous rather than preferring the int overload since both overloads are callable and now just have template parameters as arguments. I've put a lot of thought (on/off for years) into trying to come up with a tie_overloads that actually works precisely as an overload set and I'm reasonably certain that it cannot be done, but I am unable to say that for certain.
On the plus side, I've never actually found the lack of a tie_overloads to be a problem, since in times that I've personally wanted it it's been easy to manually make a reference-semantic function object at the call-site via lambdas. The main difficulty is that this just can't be done automatically inside of generic code, so the value-semantics of the function object passing sometimes bleeds out to the user a little bit in generic code (the user just needs to be aware the function objects are copied/moved in). In practice this isn't much of a problem since standard library algorithms take function objects by value anyway and so people are familiar with those semantics.
And on another note, even if we had the overloads() function template, it would still make sense to me to have something like match( ... ) that is just an alias for apply_visitor( overloads( ... ) ). I would even make it a member, in which case it would probably be more properly called 'apply'. :-)
True. There is also one thing that I do like about match -- it's possible to make one that requires an /exact/ match (I.E. in places where you want to handle each case individually and don't want overload resolution, more similar to a type switch). So both match (generalized for the n-ary case) and overloads still seem useful in different scenarios. I know another booster on this list has implemented this, though I don't think it's been posted.
I have started two separated C++ proposals for overload [1] and match [2] In case some have suggestions for improving them, Vicente [1] https://github.com/viboes/tags/blob/master/doc/proposals/overload/DXXXX_Over... [2] https://github.com/viboes/tags/blob/master/doc/proposals/match/DXXXX_Match.m...
2015-01-09 8:10 GMT+03:00 Vicente J. Botet Escriba <vicente.botet@wanadoo.fr
: <...>
I have started two separated C++ proposals for overload [1] and match [2]
In case some have suggestions for improving them, Vicente
[1] https://github.com/viboes/tags/blob/master/doc/ proposals/overload/DXXXX_Overload.md [2] https://github.com/viboes/tags/blob/master/doc/proposals/match/DXXXX_Match.m...
I like the idea of `overload` more than the `match` idea. `match` looks like a function that does two things: constructs an overloaded function object and applies it as a visitor. While the first thing could be widely usable, the visitation part applies only to the variant. This makes the `overload` method (that is not bound to variant) more usable than `match`. -- Best regards, Antony Polukhin
On 2015-01-09 10:06, Antony Polukhin wrote:
I like the idea of `overload` more than the `match` idea.
I see many people prefer going through apply_visitor() with overload(). Coming from a functional programming perspective, this is kinda backwards. What we need is sum types. C++ unions are sort of sum types, but they're crap, so variant<> is our C++ workaround. However, somewhere along the line it was decided that variant<> was to be accessed /by type/ (either through get<T>() or apply_visitor()), which means that variant<T, T> does not work well at all. This means variant<> fails to be the building block that sum types can/should be, because you cannot use a variant<A, B> in a generic context the way you use, say, Either, in Haskell, because while Either A B works just fine when A and B might end up being the same type (because you can still distinguish the Left and Right constructors just fine), variant<A, B> with its type-based access breaks down when A and B end up being the same type. Really, variant<> should have been index-based all along, so that you can just do get<0> and get<1> on a variant<T, T> (or a variant<A, B> where A might be B) without losing information. Analogously, match(v, [](T){}, [](T){}) on such a variant would Just Work (again, without losing information), while apply_visitor() with overload() breaks down. There might be legit uses for a type-indexed variant, but really, proper sums should have been the #1 priority, and it's a damn shame that variant<> didn't get it right. </2cents>
Eelis wrote:
Really, variant<> should have been index-based all along, so that you can just do get<0> and get<1> on a variant<T, T> (or a variant<A, B> where A might be B) without losing information.
Adding get<I> to variant is trivial - it already stores the index and exposes it in which(). But you can't store anything into variant<T,T> because constructors and assignment dispatch by type. We could in principle add something like variant<T,T> v( index<0>, t ); and v.assign( index<0>, t ); or variant<T,T> v( _1, t ); v.assign( _1, t ); but v = t; is never going to work. The best we could do is v = { _1, t };
Le 09/01/15 18:52, Eelis a écrit :
On 2015-01-09 10:06, Antony Polukhin wrote:
I like the idea of `overload` more than the `match` idea.
I see many people prefer going through apply_visitor() with overload(). Coming from a functional programming perspective, this is kinda backwards.
What we need is sum types. C++ unions are sort of sum types, but they're crap, so variant<> is our C++ workaround.
However, somewhere along the line it was decided that variant<> was to be accessed /by type/ (either through get<T>() or apply_visitor()), which means that variant<T, T> does not work well at all.
Using tagged types you can always have variant<tagged<T, left>, tagged<T, right>> or variant<left<T>, right<T>>.
This means variant<> fails to be the building block that sum types can/should be, because you cannot use a variant<A, B> in a generic context the way you use, say, Either, in Haskell, because while Either A B works just fine when A and B might end up being the same type (because you can still distinguish the Left and Right constructors just fine), variant<A, B> with its type-based access breaks down when A and B end up being the same type. either<A,B> ~~ variant<left<A>, right<B>>.
Haskel dont allow to define a sum type with the same type data X a b = a | b is invalid. The reason is that you can not distinguish one of the other. You need to use type constructors as Left or Right on the case of Either data Either a b = Left a | Right b
Really, variant<> should have been index-based all along, so that you can just do get<0> and get<1> on a variant<T, T> (or a variant<A, B> where A might be B) without losing information.
Well, it is not that I'm against the design, but A | A seems to me that is equal to A ;-)
Analogously, match(v, [](T){}, [](T){}) on such a variant would Just Work (again, without losing information), while apply_visitor() with overload() breaks down.
There might be legit uses for a type-indexed variant, but really, proper sums should have been the #1 priority, and it's a damn shame that variant<> didn't get it right.
I would add that there might be legit uses for nu-indexed variants, but really, proper sums are better ;-) Best, Vicente
On Fri, Jan 9, 2015 at 9:52 AM, Eelis <eelis@eelis.net> wrote:
On 2015-01-09 10:06, Antony Polukhin wrote:
I like the idea of `overload` more than the `match` idea.
I see many people prefer going through apply_visitor() with overload(). Coming from a functional programming perspective, this is kinda backwards.
I disagree completely. Please explain what you consider apply_visitor and overload to be "backwards"? All apply_visitor (or apply, or visit, or dispatch, or whatever the name) is is a high order function that takes a function object and invokes it with the trailing arguments upon transformation. There is nothing at all backwards about this from a functional programming perspective or any perspective.
What we need is sum types. C++ unions are sort of sum types, but they're crap, so variant<> is our C++ workaround.
While I agree that they are important, I would hardly call variant a "workaround" to not having language-level discriminated sum types, it's just a library implementation of that kind of sum type. In general, I am of the strong opinion that if a type can be efficiently implemented either as a library component or as a language feature, all else being equal, one should prefer it as a library component. In the case of something like variant, there are some benefits that can come from language support, but there are also some trade-offs with respect to a C++ implementation that, IMO, are not really a good idea to try to standardize at the core language level. Even if we had them, I have little doubt that there would still exist libraries that make alternative trade-offs anyway. However, somewhere along the line it was decided that variant<> was to be
accessed /by type/ (either through get<T>() or apply_visitor()), which means that variant<T, T> does not work well at all.
That's not why a variant has no repeated types. It is also not expected to be the only sum type abstraction is applicable to all situations.
This means variant<> fails to be the building block that sum types can/should be
Not true. It is trivial to build a generalized discriminated union on top of variant. All you do is have the discriminated_union<T0, T1, T2, ...> template contain a variant<wrapper<T0, 0>, wrapper<T1, 1>, wrapper<T2, 2>...>. It is also trivial to go the other way around -- build a variant on top of discriminated_union. That said, in practice, I've personally found it best to build them both from scratch on top of lower-level primitives and to have them both model the same concept, allowing each to be visited in the same way. FWIW I have my own library implementation of, and was planning to propose for standardization: // Non-discriminated, variadic template<class...> class union_; // Non-discriminated, variadic, and guarantees destructibility template<class...> class descructible_union; // The type that you want template<class...> class discriminated_union; // Similar to boost template<class...> class variant;
Really, variant<> should have been index-based all along, so that you can just do get<0> and get<1> on a variant<T, T> (or a variant<A, B> where A might be B) without losing information. Analogously, match(v, [](T){}, [](T){}) on such a variant would Just Work (again, without losing information), while apply_visitor() with overload() breaks down.
You seem to be not getting that discriminated_union and variant each are used in different scenarios. One quick scenario for where you want a variant is: // Holds one of these collidable shapes. variant<circle, square, triangle> a, b; struct in_collision_t { bool operator()(circle, circle) const; bool operator()(circle, square) const; bool operator()(circle, triangle) const; // etc. } constexpr in_collision{}; // Use it when you don't have variants const bool collide = in_collision( circle{}, square{} ); // Use it when you do have variants const bool collide_polymorphic = apply( in_collision, a, b ); I use this type of functionality a lot. Much more so, actually, than a general discriminated_union. IMO, both are very useful abstractions to have, but it would be in error to say that one is "better" than the other. They are simply different abstractions that are implemented in a similar manner. -- -Matt Calabrese
On 01/09/2015 12:35 PM, Matt Calabrese wrote:
On Fri, Jan 9, 2015 at 9:52 AM, Eelis <eelis@eelis.net> wrote:
[snip]
What we need is sum types. C++ unions are sort of sum types, but they're crap, so variant<> is our C++ workaround.
[snip]
That's not why a variant has no repeated types.
I thought it didn't allow repeated types because accessing by the type would then be ambiguous. IOW, for variant<T,T>, which element would get<T>(variant<T,T>*) get, the 1st or 2nd? [snip]
This means variant<> fails to be the building block that sum types can/should be
Not true. It is trivial to build a generalized discriminated union on top of variant. All you do is have the discriminated_union<T0, T1, T2, ...> template contain a variant<wrapper<T0, 0>, wrapper<T1, 1>, wrapper<T2, 2>...>.
Sure, but then all the template progamming designed to get the discriminant based on type becomes redundant.
It is also trivial to go the other way around -- build a variant on top of discriminated_union.
Which seems simpler to me. After all, implementing the discriminated union involves, at first, just the "primitives": 1) calculating the size an alignment of a buffer 2) using the tag to find the right type in a type list as in the mpl::at_c<tag>: http://www.boost.org/doc/libs/1_31_0/libs/mpl/doc/ref/Reference/at_c.html Using variant, step 1 is the same, but how is step 2 implemented? It seems it would involve a metaprogram to search through the bounded types to find a match. Isn't that more complicated and compile-time intensive? Why not 1st do the simpler discriminated union, and then, if someone wants the type variant, implement that on top of the discriminated union?
That said, in practice, I've personally found it best to build them both from scratch on top of lower-level primitives
Could you describe the lower-level primitives? [snip] -regards, Larry
On Fri, Jan 9, 2015 at 1:24 PM, Larry Evans <cppljevans@suddenlink.net> wrote:
Which seems simpler to me. After all, implementing the
discriminated union involves, at first, just the "primitives":
I've implemented variant on top of discriminated_union and I've implemented discriminated_union on top of variant. To be honest, both solutions leave a lot to be desired although neither is particularly difficult to do. The types I actually use aren't built on one another, but are built on shared, lower-level constructs. 1) calculating the size an alignment of a buffer
You actually don't do this when implementing a discriminated union type (nor variant) anymore. You need to use an *actual* union underneath the hood in order for some degree of constexpr-ness in places that it is possible. This is one reason for the union_ and destructible_union templates in my earlier reply (they use a cons-like recursive union structure underneath the hood, which is an approach that Eric Niebler suggested and that I've seen done elsewhere as well). They are necessary as implementation details and useful on their own when discrimination isn't required. They support fully forwarded construction of the members along with access by index, etc. 2) using the tag to find the right type in a type list as
in the mpl::at_c<tag>:
Using variant, step 1 is the same, but how is step 2 implemented?
I'm sure you are making a good point but I'm having trouble understanding what specifically you are saying here. To be clear, I agree that in theory it makes more sense to implement variant on top of discriminated_union, but having implemented each on top of the other in the past, they both have different problems, though neither is too difficult to do and you don't lose any functionality. It just ends up being that the implementation still has some complexities and the compile-times take a noticeable hit, so building them each from exactly their shared requirements ends up working better.
Could you describe the lower-level primitives?
The union_ and destructible_union templates, along with an unfortunately complicated set of chained CRTP bases that are required to guarantee that each of the special member functions are individually declared and defined (and possibly trivial) exactly when they can be. I didn't elaborate earlier, but the reason destructible_union exists is because with the way C++ union rules are, if any of the members has a destructor that is non-trivial, the overall union itself is not descructible at all. This "not-defined-if-any-are-nontrivial" is true of all of the special member functions with respect to modern unions, but allowing destructibility is important here because while you can encapsulate a type that is, for instance, not copyable, and then give the containing type a copy constructor, you cannot encapsulate a type that is not destructible and then give the containing type a destructor. Instead, the union itself needs to be destructible. So, destructible_union is equivalent to union_ in cases where all of the destructors were trivial, but in the case that any of them are not trivial, it defines its own destructor that does nothing. This way it can be used when implementing a variant or a discriminated_union (or other constructs). -- -Matt Calabrese
On 01/09/2015 04:05 PM, Matt Calabrese wrote:> On Fri, Jan 9, 2015 at 1:24 PM, Larry Evans <cppljevans@suddenlink.net>
wrote:
Which seems simpler to me. After all, implementing the
discriminated union involves, at first, just the "primitives":
I've implemented variant on top of discriminated_union and I've implemented discriminated_union on top of variant. To be honest, both solutions leave a lot to be desired although neither is particularly difficult to do. The types I actually use aren't built on one another, but are built on shared, lower-level constructs.
1) calculating the size an alignment of a buffer
You actually don't do this when implementing a discriminated union type (nor variant) anymore. You need to use an *actual* union underneath the hood in order for some degree of constexpr-ness in places that it is possible.
Where is constexpr-ness needed? I used it to calcutate both the size and alignment; however, since you're using these "actual unions", you don't need this( as you say above).
This is one reason for the union_ and destructible_union templates in my earlier reply (they use a cons-like recursive union structure underneath the hood, which is an approach that Eric Niebler suggested and that I've seen done elsewhere as well).
I'm guessing that this method of using "a cons-like recursive union" is somewhat like how fusion or mpl does it; however, wouldn't this require using the preprocessor to, for example, generate something like: union actual_union0 { T0 m0; T1 m1; . . . T9 m9; }; union actual_union10 { actual_union0 au0;//the recursion T10 m10; T11 m11; . . . T19 m19; }; . . . ? Is that even close to what you're describing? [snip] TIA. -regards, Larry
On 2015-01-09 19:35, Matt Calabrese wrote:
You seem to be not getting that discriminated_union and variant each are used in different scenarios.
One quick scenario for where you want a variant is:
Why do you want a variant here? Can't you define an apply() for discriminated_union that does exactly what you want? Why do you need a different type? More specifically, why do you need a type for which match(v, [](A a){...}, [](B b){...} ) randomly breaks in generic code?
On 01/09/2015 11:52 AM, Eelis wrote:
On 2015-01-09 10:06, Antony Polukhin wrote:
I like the idea of `overload` more than the `match` idea.
I see many people prefer going through apply_visitor() with overload(). Coming from a functional programming perspective, this is kinda backwards.
What we need is sum types. C++ unions are sort of sum types, but they're crap, so variant<> is our C++ workaround.
However, somewhere along the line it was decided that variant<> was to be accessed /by type/ (either through get<T>() or apply_visitor()), which means that variant<T, T> does not work well at all.
This means variant<> fails to be the building block that sum types can/should be, because you cannot use a variant<A, B> in a generic context the way you use, say, Either, in Haskell, because while Either A B works just fine when A and B might end up being the same type (because you can still distinguish the Left and Right constructors just fine), variant<A, B> with its type-based access breaks down when A and B end up being the same type.
Really, variant<> should have been index-based all along, so that you can just do get<0> and get<1> on a variant<T, T> (or a variant<A, B> where A might be B) without losing information. Analogously, match(v, [](T){}, [](T){}) on such a variant would Just Work (again, without losing information), while apply_visitor() with overload() breaks down.
There might be legit uses for a type-indexed variant, but really, proper sums should have been the #1 priority, and it's a damn shame that variant<> didn't get it right.
FYI, there exists an implementation of what you're talking about here: https://svn.boost.org/svn/boost/sandbox/variadic_templates/boost/composite_s... The tags can be specified as enumerators of an enumerations so that one can say get<Enumerator1>(variant) where,for example: enum enumerations { enumerator1 , enumerator2 ... , enumeratorN }; Unfortunately, for visitation, the compile times were much slower and reported on this mailing list several years ago. I'm now trying to use generalized constexpr to see if that will make any difference. -regards, Larry
Le 09/01/15 10:06, Antony Polukhin a écrit :
2015-01-09 8:10 GMT+03:00 Vicente J. Botet Escriba <vicente.botet@wanadoo.fr
: <...>
I have started two separated C++ proposals for overload [1] and match [2]
In case some have suggestions for improving them, Vicente
[1] https://github.com/viboes/tags/blob/master/doc/ proposals/overload/DXXXX_Overload.md [2] https://github.com/viboes/tags/blob/master/doc/proposals/match/DXXXX_Match.m...
I like the idea of `overload` more than the `match` idea. `match` looks like a function that does two things: constructs an overloaded function object and applies it as a visitor. While the first thing could be widely usable, the visitation part applies only to the variant. This makes the `overload` method (that is not bound to variant) more usable than `match`.
As I said, match can be applied to any sum type. The github repository contain examples for boost::variant, either(boost::optional) and a selection of possible types for boost::any. It can be applied also to expected, any smart pointer, boost::future or any probably valued type. Hopping this helps to describe the motivation for a function that makes code much safer than using directly functions to check if there is a value and to get it. Best, Vicente
My personal stance and I what I use on projects is something a little different and I try to evangelize: Make an overloads template that just works with apply_visitor or an apply_visitor like function:
apply_visitor( overloads( [](A & a){ cout << "it's a A!"; } [](B & b){ cout << "oh, a B!"; } [](C & c){ cout << "ah yes, a C!"; } [](auto & d){ cout << "a default!"; } ),
v
);
I use syntax like this: case_<void>(v)( [](A & a){ cout << "it's a A!"; } [](B & b){ cout << "oh, a B!"; } [](C & c){ cout << "ah yes, a C!"; } [](auto & d){ cout << "a default!"; } ); Which I think is a little neater. You can implement this in C++14 with Fit like this: template<class T, class Result> Result case_(T& x) { return [&](auto&&... fs) { return apply_visitor(x, fit::result<Result>(fit::match(std::forward<decltype(fs)>(fs)...)) ); }; } This could also be extended to support multiple variables as well. Paul -- View this message in context: http://boost.2283326.n4.nabble.com/variant-match-tp4670714p4670758.html Sent from the Boost - Dev mailing list archive at Nabble.com.
I use syntax like this:
case_<void>(v)( [](A & a){ cout << "it's a A!"; } [](B & b){ cout << "oh, a B!"; } [](C & c){ cout << "ah yes, a C!"; } [](auto & d){ cout << "a default!"; } );
Shouldn't it be switch_ instead of case_?
I used `case_` because I was wanting to mimic OCaml syntax, but `switch_` might make more sense for C++. Paul -- View this message in context: http://boost.2283326.n4.nabble.com/variant-match-tp4670714p4670761.html Sent from the Boost - Dev mailing list archive at Nabble.com.
Unfortunately, no, because at that point you've "flattened" the operator() to having all template parameters. Overload resolution would no longer be able to produce better or worse matches when one or more of the passed-in function objects are callable. For instance, if you pass in tie_overloads( [](int a) {}, [](auto a) ) to apply_visitor, if the variant contained an "int" then the function call would actually be ambiguous rather than preferring the int overload since both overloads are callable and now just have template parameters as arguments. I've put a lot of thought (on/off for years) into trying to come up with a tie_overloads that actually works precisely as an overload set and I'm reasonably certain that it cannot be done, but I am unable to say that for certain.
Also, additionally, the Fit library provides the `fit::conditional` which find the first match(whereas `fit::match` finds the best match). So by the nature of how it works, it can be used even when the functions are flattened. So if you wanted to use references you could do this instead: apply_visitor( fit::conditional( std::ref([](A & a){ cout << "it's a A!"; }), std::ref([](B & b){ cout << "oh, a B!"; }), std::ref([](C & c){ cout << "ah yes, a C!"; }), std::ref([](auto & d){ cout << "a default!"; }) ), v); Of course the semantics are different. Ultimately, `fit::conditional` is designed for overloading with conditional constraints like this: #define IF(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0 apply_visitor( fit::conditional( [](auto & range, IF( is_range<decltype(range)>() )){ cout << "it's a range"; }, [](auto & sequence, IF( is_sequence<decltype(sequence)>() )){ cout << "oh, a sequence!"; }, [](auto & d){ cout << "a default!"; } ), v); Which is equivalent to the following: apply_visitor( fit::conditional([](auto& x) { using T = decltype(x); if (is_range<T>()) cout << "it's a range"; else if (is_sequence<T>()) cout << "oh, a sequence!"; else cout << "a default!"; }), v); Hence the name conditional. Paul -- View this message in context: http://boost.2283326.n4.nabble.com/variant-match-tp4670714p4670760.html Sent from the Boost - Dev mailing list archive at Nabble.com.
participants (9)
-
Antony Polukhin
-
Eelis
-
Larry Evans
-
Matt Calabrese
-
Peter Dimov
-
pfultz2
-
Rob Stewart
-
TONGARI J
-
Vicente J. Botet Escriba