Closed daniel-j-h closed 7 years ago
@daniel-j-h - Great idea ^ ! How about standalone function in variant_lambda.hpp header so it can be optionally included by client code ?
Why an extra file? It's literally 20 lines of code?
@kkaefer - variant.hpp
is getting quite large and not everyone will use lambdas. This is why we have variant_io.hpp
,recursive_wrapper.hpp
etc. Modularity rather than encapsulation !
Fair point, but then we also should move the visitor functions to an extra file?
Any updates on the issues raised above? I still think it would be great to get this in. Open questions:
variant.hpp
@daniel-j-h
match
sounds reasonable to mePR is most welcome!
auto v = match([](Left){},
[](Right){});
apply_visitor(v, my_variant);
vs
match(v, [](Left){},
[](Right){});
@daniel-j-h : can we have both ? :)
D'oh! I've been working on this in august but then got totally distracted and forgot to comment. I even had questions regarding current interface but I'll have to go through the code to recall what they were.
My suggestions:
variant.hpp
, because
make_visitor
visit
member function overload for 2+ args, that will visit(make_visitor(...))
One of the questions about current interface: why is variant::visit
static?
why is variant::visit static?
Because variant::visit
doesn't modify internal state and making it static gives compiler some hints on how to generate binary code. Why are you thinking it shouldn't be static
?
this is not a question of modularity, it's essentially syntax sugar; it should be available right off the bat
Well, the fact it's a syntactic sugar
and not a part of core interface makes strong argument towards a separate header. In principal, I'd like to see more boost
like approach to organising header-only libraries. We can include <detail/match.hpp>
in <variant.hpp>
as an experiment to see if compile/parse times are acceptable but not the other way around !
To me that sounds like having unique_ptr
and make_unique
in different headers.
I see this proposal as the way to write quick one-off visitors, not some outlandish feature that deserves a usage barrier. If there's something that deserves being moved into a separate header, then it's binary visitor.
Anyway, if you want to chop variant into smaller pieces, then I prefer having variant.hpp
include all of them rather than an arbitrary subset.
edit: here's my take on @daniel-j-h's original proposal.
Anyway, if you want to chop variant into smaller pieces, then I prefer having variant.hpp include all of them rather than an arbitrary subset.
^ This is exactly my intention and we need to start somewhere - this is a good place to start.
@lightmare - +1 for re-factoring binary_visitor
into separate header
This is a good feature and it feels like a good time to get a consensus and move on this :).
@daniel-j-h do you have any feedback re: lightmare's take ? I like make_visitor
interface. Also, you added MIT
license to the original gist while variant is BSD - just wondering.
To summarise: this issue needs a PR we can all work on to get this into the next release.
/cc @springmeyer @daniel-j-h @lightmare
No reason for the MIT vs BSD license - some users pinged me via mail and wanted to know under which conditions they can just drop it into their projects.
Also a user reported my original proof of concept (Gist linked above) not properly handling degenerate variants (variants with a single type). I just updated the Gist adding the missing constructor for the recursion's base-case.
We should have tests for edge cases like this, for sure.
@lightmare's take looks good overall - we could take the functions as forwarding references and std::forward
them on, e.g. here.
we could take the functions as forwarding references and std::forward them on
I don't remember why I made it take arguments by value. Should've made a comment, I guess ;) Maybe it was because I only wanted to show how it'd work, without unnecessary clutter of proper implementation. Or maybe I thought it wasn't necessary.
If make_visitor
and unary_visitor
constructor take arguments by value, and only std::move
when passing them, they will be copied/moved into make_visitor
, then moved into unary_visitor
constructor, then moved into its bases.
On the other hand, if make_visitor
takes forwarding references, unary_visitor
has to inherit from std::decay
ed functions, and the copying/moving happens in base class constructors. So you avoid two moves that I think the compiler shouldn't have trouble optimizing away.
Suppose you're using the common
Either
sumtype, providing a choice in its type constructorsand your API returns either a
struct Error {};
or astruct Response {};
type as inEither<Error, Response>
. Providing a visitor for this variant you have to write the following handlerInstead, what about:
The idea is to inherit from the lambda's anonymous type pulling in its
operator()
This gets even better once you realize you no longer have to manually add member attributes and write constructors for your handle types. Simple lambda captures are the equivalent
Here's a proof on concept. It probably needs some more time, refinements and eyes to make this production ready, but you get the idea: https://gist.github.com/daniel-j-h/04efdc54dae50c1b9bff688ec354e693
Discussion:
match
is a bad / overloaded name, not set in stonematch
only create the visitor or combine creating and applying the visitor insteadcc @kkaefer @artemp @joto