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