A more convenient Variant visitation syntax

While Boost Variant is a wonderful library that fills a genuine hole in the C++ language, it is not as nice to use as it could be. The "correct" way to unwrap a variant is to apply a static visitor, but this method carries a high syntactic burden because it needs the user to define a class (probably somewhere far away) just so they can "switch" on the type of the object stored in the variant. I came up with a solution where the handler functions can be specified inline with C++11 lambdas. Here's an example: boost::variant< int, std::string > u("hello world"); int result = match(u, [](int i) -> int { return i; }, [](const std::string & str) -> int { return str.length(); }); The `match` function accepts the variant followed by the handler functions. The functions can be specified in any order. It will fail to compile if the functions do not match the types of the variant, if the return types are not all the same, or if a non-unary function is supplied. This is C++11 only because it's quite pointless without lambda functions. I've tested it with gcc 4.7. It doesn't work on gcc 4.6, which can't handle the variadic templates. Here's the code: https://github.com/exclipy/inline_variant_visitor What do you think? This started out for me as an exercise to learn template metaprogramming and C++11, but hopefully, it can actually evolve into something useful.

On 14/04/12 17:00, Kevin Wu Won wrote:
While Boost Variant is a wonderful library that fills a genuine hole in the C++ language, it is not as nice to use as it could be. The "correct" way to unwrap a variant is to apply a static visitor, but this method carries a high syntactic burden because it needs the user to define a class (probably somewhere far away) just so they can "switch" on the type of the object stored in the variant.
I came up with a solution where the handler functions can be specified inline with C++11 lambdas. Here's an example:
boost::variant< int, std::string> u("hello world"); int result = match(u, [](int i) -> int { return i; }, [](const std::string& str) -> int { return str.length(); });
The `match` function accepts the variant followed by the handler functions. The functions can be specified in any order. It will fail to compile if the functions do not match the types of the variant, if the return types are not all the same, or if a non-unary function is supplied.
This is C++11 only because it's quite pointless without lambda functions. I've tested it with gcc 4.7. It doesn't work on gcc 4.6, which can't handle the variadic templates.
It can be done in C++03 and expression-template-based lambdas just fine as well. All you need to do is generate a function object that inherits recursively from a set of other function objects. The function objects in question need to be either monomorphic or to be sufficiently constrained with SFINAE so that each operator() overload is not ambiguous. Unfortunately, Boost.Phoenix was never extended to support this.

On Sat, Apr 14, 2012 at 5:41 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
On 14/04/12 17:00, Kevin Wu Won wrote:
While Boost Variant is a wonderful library that fills a genuine hole in the C++ language, it is not as nice to use as it could be. The "correct" way to unwrap a variant is to apply a static visitor, but this method carries a high syntactic burden because it needs the user to define a class (probably somewhere far away) just so they can "switch" on the type of the object stored in the variant.
I came up with a solution where the handler functions can be specified inline with C++11 lambdas. Here's an example:
boost::variant< int, std::string> u("hello world"); int result = match(u, [](int i) -> int { return i; }, [](const std::string& str) -> int { return str.length(); });
The `match` function accepts the variant followed by the handler functions. The functions can be specified in any order. It will fail to compile if the functions do not match the types of the variant, if the return types are not all the same, or if a non-unary function is supplied.
This is C++11 only because it's quite pointless without lambda functions. I've tested it with gcc 4.7. It doesn't work on gcc 4.6, which can't handle the variadic templates.
It can be done in C++03 and expression-template-based lambdas just fine as well.
All you need to do is generate a function object that inherits recursively from a set of other function objects.
The function objects in question need to be either monomorphic or to be sufficiently constrained with SFINAE so that each operator() overload is not ambiguous.
Unfortunately, Boost.Phoenix was never extended to support this.
I really like the idea, and i think the C++11-based solution above is nice! I didn't have the time yet to glance through the code though ... Phoenix didn't tackle that yet because it is a hard problem ... the main problem is the syntax how to express which lambda is responsible for what types. The above solution with C++11 lambdas is nice indeed! However, what's missing is something like a "catch all" aka a templated lambda ... FWIW, independent of this thread I wanted to tackle that for the next release ... I'd like to see some propositions on how such a type based matching could be done with phoenix, which might come down to have something like pattern matching in functional languages ... I have no idea how to to do that yet ....

On Sat, Apr 14, 2012 at 9:39 AM, Thomas Heller <thom.heller@googlemail.com>wrote:
On Sat, Apr 14, 2012 at 5:41 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
On 14/04/12 17:00, Kevin Wu Won wrote:
While Boost Variant is a wonderful library that fills a genuine hole in the C++ language, it is not as nice to use as it could be. The "correct" way to unwrap a variant is to apply a static visitor, but this method carries a high syntactic burden because it needs the user to define a class (probably somewhere far away) just so they can "switch" on the type of the object stored in the variant.
I came up with a solution where the handler functions can be specified inline with C++11 lambdas. Here's an example:
boost::variant< int, std::string> u("hello world"); int result = match(u, [](int i) -> int { return i; }, [](const std::string& str) -> int { return str.length(); });
The `match` function accepts the variant followed by the handler functions. The functions can be specified in any order. It will fail to compile if the functions do not match the types of the variant, if the return types are not all the same, or if a non-unary function is supplied.
This is C++11 only because it's quite pointless without lambda functions. I've tested it with gcc 4.7. It doesn't work on gcc 4.6, which can't handle the variadic templates.
It can be done in C++03 and expression-template-based lambdas just fine as well.
All you need to do is generate a function object that inherits recursively from a set of other function objects.
The function objects in question need to be either monomorphic or to be sufficiently constrained with SFINAE so that each operator() overload is not ambiguous.
Unfortunately, Boost.Phoenix was never extended to support this.
I really like the idea, and i think the C++11-based solution above is nice! I didn't have the time yet to glance through the code though ...
Phoenix didn't tackle that yet because it is a hard problem ... the main problem is the syntax how to express which lambda is responsible for what types. The above solution with C++11 lambdas is nice indeed! However, what's missing is something like a "catch all" aka a templated lambda ... FWIW, independent of this thread I wanted to tackle that for the next release ... I'd like to see some propositions on how such a type based matching could be done with phoenix, which might come down to have something like pattern matching in functional languages ... I have no idea how to to do that yet ....
Doesn't Lorenzo's Overload (or whatever name it is now) (sub)library effectively do this already? I.e., it packages up a bunch of function objects and dispatches to the (first?) one that can handle the give argument type? - Jeff

On 14/04/12 18:39, Thomas Heller wrote:
Phoenix didn't tackle that yet because it is a hard problem ... the main problem is the syntax how to express which lambda is responsible for what types.
One possible way: add information so that each node knows the type of their arguments (with fallback to a template type if unknown). Build operator() with those types instead of unconstrained templates. You may also enhance the system by automatically deducing the type of an argument even if it was not typed explicitly (e.g. because you did a bind with a member function on it). i.e. apply_visitor( variant , make_overload( bind<int>(arg1) , bind(arg1, &std::string::length) ) ); The function object resulting from bind<int>(arg1) would have an operator() overload like int operator()(int arg1); while the function object resulting from bind(arg1, &std::string::length) would have one like size_t operator()(std::string& arg1); the function object resulting from the make_overload call would have two operator() overloads like int operator()(int arg1); size_t operator()(std::string& arg1); So it will work just fine as a variant visitor.
However, what's missing is something like a "catch all" aka a templated lambda...
You're getting that simply by not typing the arguments.

on Sat Apr 14 2012, Thomas Heller <thom.heller-AT-googlemail.com> wrote:
On Sat, Apr 14, 2012 at 5:41 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
On 14/04/12 17:00, Kevin Wu Won wrote:
This is C++11 only because it's quite pointless without lambda functions. I've tested it with gcc 4.7. It doesn't work on gcc 4.6, which can't handle the variadic templates.
It can be done in C++03 and expression-template-based lambdas just fine as well.
All you need to do is generate a function object that inherits recursively from a set of other function objects.
The function objects in question need to be either monomorphic or to be sufficiently constrained with SFINAE so that each operator() overload is not ambiguous.
Unfortunately, Boost.Phoenix was never extended to support this.
I really like the idea, and i think the C++11-based solution above is nice! I didn't have the time yet to glance through the code though ...
Phoenix didn't tackle that yet because it is a hard problem ... the main problem is the syntax how to express which lambda is responsible for what types. The above solution with C++11 lambdas is nice indeed!
Yeah, the first time I've actually been happy that non-polymorphic lambdas exist! Nice idea, Kevin!
However, what's missing is something like a "catch all" aka a templated lambda ...
...ah, yes... there's the rub. And my day is now spoiled :-) -- Dave Abrahams BoostPro Computing http://www.boostpro.com

________________________________ De: Dave Abrahams <dave@boostpro.com> Para: boost@lists.boost.org Enviado: sábado, 14 de abril de 2012 22:49 Asunto: Re: [boost] A more convenient Variant visitation syntax on Sat Apr 14 2012, Thomas Heller <thom.heller-AT-googlemail.com> wrote:
On Sat, Apr 14, 2012 at 5:41 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
On 14/04/12 17:00, Kevin Wu Won wrote:
This is C++11 only because it's quite pointless without lambda functions. I've tested it with gcc 4.7. It doesn't work on gcc 4.6, which can't handle the variadic templates.
It can be done in C++03 and expression-template-based lambdas just fine as well.
All you need to do is generate a function object that inherits recursively from a set of other function objects.
The function objects in question need to be either monomorphic or to be sufficiently constrained with SFINAE so that each operator() overload is not ambiguous.
Unfortunately, Boost.Phoenix was never extended to support this.
I really like the idea, and i think the C++11-based solution above is nice! I didn't have the time yet to glance through the code though ...
Phoenix didn't tackle that yet because it is a hard problem ... the main problem is the syntax how to express which lambda is responsible for what types. The above solution with C++11 lambdas is nice indeed!
Yeah, the first time I've actually been happy that non-polymorphic lambdas exist! Nice idea, Kevin!
However, what's missing is something like a "catch all" aka a templated lambda ...
...ah, yes... there's the rub. And my day is now spoiled :-) There is a catch all case. Just provide a lambda for boost::blank. See the original thread on StackOverflow where Me and R. Martinho Fernandes came up with the template syntax. There is also a complete code snippet with test cases on the post http://stackoverflow.com/q/7867555/170521

________________________________ De: Dave Abrahams <dave@boostpro.com> Para: boost@lists.boost.org Enviado: sábado, 14 de abril de 2012 22:49 Asunto: Re: [boost] A more convenient Variant visitation syntax on Sat Apr 14 2012, Thomas Heller <thom.heller-AT-googlemail.com> wrote:
On Sat, Apr 14, 2012 at 5:41 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
On 14/04/12 17:00, Kevin Wu Won wrote:
This is C++11 only because it's quite pointless without lambda functions. I've tested it with gcc 4.7. It doesn't work on gcc 4.6, which can't handle the variadic templates.
It can be done in C++03 and expression-template-based lambdas just fine as well.
All you need to do is generate a function object that inherits recursively from a set of other function objects.
The function objects in question need to be either monomorphic or to be sufficiently constrained with SFINAE so that each operator() overload is not ambiguous.
Unfortunately, Boost.Phoenix was never extended to support this.
I really like the idea, and i think the C++11-based solution above is nice! I didn't have the time yet to glance through the code though ...
Phoenix didn't tackle that yet because it is a hard problem ... the main problem is the syntax how to express which lambda is responsible for what types. The above solution with C++11 lambdas is nice indeed!
Yeah, the first time I've actually been happy that non-polymorphic lambdas exist! Nice idea, Kevin!
However, what's missing is something like a "catch all" aka a templated lambda ...
...ah, yes... there's the rub. And my day is now spoiled :-) There is a catch all case. Just provide a lambda for boost::blank. See the original thread on StackOverflow where Me and R. Martinho Fernandes came up with the template syntax. There is also a complete code snippet with test cases on the post http://stackoverflow.com/q/7867555/170521 Charles J. Quarra

On Sat, Apr 14, 2012 at 5:00 PM, Kevin Wu Won <exclipy@gmail.com> wrote:
[...] I came up with a solution where the handler functions can be specified inline with C++11 lambdas. Here's an example:
boost::variant< int, std::string > u("hello world"); int result = match(u, [](int i) -> int { return i; }, [](const std::string & str) -> int { return str.length(); });
The `match` function accepts the variant followed by the handler functions. The functions can be specified in any order. It will fail to compile if the functions do not match the types of the variant, if the return types are not all the same, or if a non-unary function is supplied.
This is C++11 only because it's quite pointless without lambda functions. I've tested it with gcc 4.7. It doesn't work on gcc 4.6, which can't handle the variadic templates.
It should be possible to implement the same feature on gcc 4.6. Take a look at https://github.com/gpderetta/Experiments/blob/scheduler/match.hpp (test: https://github.com/gpderetta/Experiments/blob/scheduler/match_test.cc) which, given a set of monomorphic function objects create a polymorphic function object (of arbitrary arity). It can be used with boost variant like this: boost::variant< int, std::string > u("hello world"); int result = boost::apply_visitor(u, match( [](int i) { return i; }, [](const std::string & str) { return str.length(); })); The 'match' function (interestingly we used the same name :) ) works fine on gcc 4.6; the implementation is surprisingly simple and it is completely indipendeng from boost.variant. If you are interested feel free to use the code and/or incorporate it in your library (it is under the boost license). HTH, -- gpd
participants (7)
-
Charlls Quarra
-
Dave Abrahams
-
Giovanni Piero Deretta
-
Jeffrey Lee Hellrung, Jr.
-
Kevin Wu Won
-
Mathias Gaunard
-
Thomas Heller