[Interest] Value Dispatch

Hi all, This might be old hat to verteran boosters but I found it nifty. Maybe it could make a small addition to Boost. Inspired by Sean Parent's C++Now! talk and the various articles on cpp-next, I've been playing around with value semantics and C++11. I found an interesting use of pack expansion at stackoverflow and expanded on it in a local project to convert from a traditional inheritance/virtual function model to a more value-centered approach. It turns out that with pack expansion one can implement "virtual functions" without needing inheritance or pointer/reference semantics. One can consider a vrtual function as "just" a compiler-generated switch statement. With that in mind, here's an example: ----------- #include <iostream> /// This is a collection of node kinds. We use it to categorize /// various kinds of objects and dispatch to the correct routine. template<typename T, typename T::kind ...I> class kind_tuple {}; /// This is the "virtual function" dispatch mechanism. It /// statically generates a lookup table of routines, one for each /// kind. template<typename R, typename D, typename T> class dispatcher {}; /// This is the actual dispatch implementation. template<typename R, typename D, typename O, typename O::kind ...I> class dispatcher<R, D, kind_tuple<O, I...>> { private: // Call the "virtual function override" via the provided class-level // dispatcher. template<typename P, typename O::kind K, typename ...Args> static R dispatch_handler(P payload, Args ...args) { return D::template dispatch<K>(payload, args...); } public: // dispatcher is an object that knows which routine in N to call. template<typename P, typename ...Args> static R dispatch(P payload, typename O::kind kind, Args ...args) { using F = R(P, Args...); F *table[] = { dispatch_handler<P, I, Args...> ... }; return table[kind](payload, args...); } }; class vehicle { public: enum kind { Car, Bus, Boat, Train, Airplane }; typedef kind_tuple<vehicle, kind::Car, kind::Bus, kind::Boat, kind::Train, kind::Airplane> kinds; private: class drive_dispatcher { public: template<kind K> static void dispatch(vehicle v) { v.drive_impl<K>(); } }; class turn_dispatcher { public: template<kind K> static bool dispatch(vehicle v, std::string direction) { return v.turn_impl<K>(direction); } }; template<kind K> void drive_impl(void) {} template<kind K> bool turn_impl(std::string direction) { return false; } kind the_kind; public: vehicle(kind k) : the_kind(k) {} void drive(void) { dispatcher<void, drive_dispatcher, kinds>::dispatch(*this, this->the_kind); } // Return whether the turn was successfully completed. bool turn(std::string direction) { return dispatcher<bool, turn_dispatcher, kinds>:: dispatch(*this, this->the_kind, direction); } }; template<> void vehicle::drive_impl<vehicle::Car>(void) { std::cout << "Driving a car!\n"; } template<> void vehicle::drive_impl<vehicle::Bus>(void) { std::cout << "Driving a bus!\n"; } template<> void vehicle::drive_impl<vehicle::Boat>(void) { std::cout << "Driving a boat!\n"; } template<> void vehicle::drive_impl<vehicle::Train>(void) { std::cout << "Driving a train!\n"; } template<> void vehicle::drive_impl<vehicle::Airplane>(void) { std::cout << "Flying an airplane!\n"; } template<> bool vehicle::turn_impl<vehicle::Car>(std::string direction) { std::cout << "Turning a car " << direction << "!\n"; return true; } template<> bool vehicle::turn_impl<vehicle::Bus>(std::string direction) { std::cout << "Turning a bus " << direction << "!\n"; return true; } template<> bool vehicle::turn_impl<vehicle::Boat>(std::string direction) { std::cout << "Turning a boat " << direction << "!\n"; return true; } template<> bool vehicle::turn_impl<vehicle::Train>(std::string direction) { std::cout << "Turning a train " << direction << "?!?\n"; return false; } template<> bool vehicle::turn_impl<vehicle::Airplane>(std::string direction) { std::cout << "Turning an airplane " << direction << "!\n"; return true; } template<typename Iterator> void drive(Iterator start, Iterator end) { for (auto i = start; i != end; ++i) i->drive(); } template<typename Iterator> void turn(Iterator start, Iterator end, std::string direction) { for (auto i = start; i != end; ++i) if (i->turn(direction)) { std::cout << "Successfully turned " << direction << "\n"; } else { std::cout << "Could not turn " << direction << "\n"; } } int main(void) { vehicle rides[] = { vehicle(vehicle::kind::Car), vehicle(vehicle::kind::Bus), vehicle(vehicle::kind::Boat), vehicle(vehicle::kind::Train), vehicle(vehicle::kind::Airplane) }; drive(rides, rides + 5); std::cout << "\n"; turn(rides, rides + 5, "left"); return 0; } ----------- The central idea is the creating of a "virtual function table" via pack expansion. Not only can we keep value semantics and avoid coupling via inheritance, we also get virtual function-like behavior for template functions. Expanding the idea allows us to implement a completely generic GoF visitor without using inheritance at all: ----------- #include <iostream> #include <tuple> /// This is a collection of node kinds. We use it to categorize /// various kinds of objects and dispatch to the correct routine. template<typename T, typename T::kind ...I> class kind_tuple {}; /// This is the "virtual function" dispatch mechanism. It /// statically generates a lookup table of routines, one for each /// kind. template<typename R, typename D, typename T> class dispatcher {}; /// This is the actual dispatch implementation. template<typename R, typename D, typename O, typename O::kind ...I> class dispatcher<R, D, kind_tuple<O, I...>> { private: // Call the "virtual function override" via the provided class-level // dispatcher. template<typename P, typename O::kind K, typename ...Args> static R dispatch_handler(P payload, Args ...args) { return D::template dispatch<K>(payload, args...); } public: // dispatcher is an object that knows which routine in N to call. template<typename P, typename ...Args> static R dispatch(P payload, typename O::kind kind, Args ...args) { using F = R(P, Args...); F *table[] = { dispatch_handler<P, I, Args...> ... }; return table[kind](payload, args...); } }; class vehicle { public: enum kind { Car, Bus, Boat, Train, Airplane }; typedef kind_tuple<vehicle, kind::Car, kind::Bus, kind::Boat, kind::Train, kind::Airplane> kinds; private: kind the_kind; public: vehicle(kind k) : the_kind(k) {} kind get_kind(void) const { return the_kind; } }; // U is to distinguish among different kinds of visitors for the same // object type. template<typename O, typename U, typename R, typename ...Args> class visitor { private: typedef visitor<O, U, R, Args...> This; // This dispatcher expects a single object so wrap the visitor and // the visited object together. typedef std::tuple<This *, O> payload; class visit_dispatcher { public: template<typename O::kind K> static R dispatch(payload object, Args ...args) { return std::get<0>(object)-> template visit_impl<K>(std::get<1>(object), args...); } }; template<typename O::kind K> R visit_impl(O object, Args ...args) {} public: R visit(O object, Args ...args) { return dispatcher<R, visit_dispatcher, typename O::kinds>:: dispatch(std::make_tuple(this, object), object.get_kind(), args...); } }; // Visitor types class Driving {}; class Turning {}; template<> template<> void visitor<vehicle, Driving, void>::visit_impl<vehicle::Car>(vehicle v) { std::cout << "Driving a car!\n"; } template<> template<> void visitor<vehicle, Driving, void>::visit_impl<vehicle::Bus>(vehicle v) { std::cout << "Driving a bus!\n"; } template<> template<> void visitor<vehicle, Driving, void>::visit_impl<vehicle::Boat>(vehicle v) { std::cout << "Driving a boat!\n"; } template<> template<> void visitor<vehicle, Driving, void>::visit_impl<vehicle::Train>(vehicle v) { std::cout << "Driving a train!\n"; } template<> template<> void visitor<vehicle, Driving, void>::visit_impl<vehicle::Airplane>(vehicle v) { std::cout << "Flying an airplane!\n"; } template<> template<> bool visitor<vehicle, Turning, bool, std::string>:: visit_impl<vehicle::Car>(vehicle v, std::string direction) { std::cout << "Turning a car " << direction << "!\n"; return true; } template<> template<> bool visitor<vehicle, Turning, bool, std::string>:: visit_impl<vehicle::Bus>(vehicle v, std::string direction) { std::cout << "Turning a bus " << direction << "!\n"; return true; } template<> template<> bool visitor<vehicle, Turning, bool, std::string>:: visit_impl<vehicle::Boat>(vehicle v, std::string direction) { std::cout << "Turning a boat " << direction << "!\n"; return true; } template<> template<> bool visitor<vehicle, Turning, bool, std::string>:: visit_impl<vehicle::Train>(vehicle v, std::string direction) { std::cout << "Turning a train " << direction << "?!?\n"; return false; } template<> template<> bool visitor<vehicle, Turning, bool, std::string>:: visit_impl<vehicle::Airplane>(vehicle v, std::string direction) { std::cout << "Turning an airplane " << direction << "!\n"; return true; } template<typename Iterator> void drive(Iterator start, Iterator end) { visitor<vehicle, Driving, void> driver; for (auto i = start; i != end; ++i) driver.visit(*i); } template<typename Iterator> void turn(Iterator start, Iterator end, std::string direction) { visitor<vehicle, Turning, bool, std::string> turer; for (auto i = start; i != end; ++i) if (turer.visit(*i, direction)) { std::cout << "Successfully turned " << direction << "\n"; } else { std::cout << "Could not turn " << direction << "\n"; } } int main(void) { vehicle rides[] = { vehicle(vehicle::kind::Car), vehicle(vehicle::kind::Bus), vehicle(vehicle::kind::Boat), vehicle(vehicle::kind::Train), vehicle(vehicle::kind::Airplane) }; drive(rides, rides + 5); std::cout << "\n"; turn(rides, rides + 5, "left"); return 0; } ----------- Not only is inheritance not required, we only need single dispatch to implement the visitor. Because we don't use inheritance at all for these "virtual functions" and the above visitor we can maintain value semantics throughout any code that uses the above techniques. Of course the code above can be cleaned up and beautified. It's just a quick proof-of-concept. I'm sure someone has already posted about this technique somewhere. Is there any interest in expanding it into a Boost library? -Dave

On 08/15/12 08:49, *smgreened@obbligato.org wrote:
Hi all,
This might be old hat to verteran boosters but I found it nifty. Maybe it could make a small addition to Boost.
Inspired by Sean Parent's C++Now! talk and the various articles on cpp-next, I've been playing around with value semantics and C++11.
I found an interesting use of pack expansion at stackoverflow and expanded on it in a local project to convert from a traditional inheritance/virtual function model to a more value-centered approach.
It turns out that with pack expansion one can implement "virtual functions" without needing inheritance or pointer/reference semantics. One can consider a vrtual function as "just" a compiler-generated switch statement. With that in mind, here's an example:
----------- [snip] class vehicle { public: enum kind { Car, Bus, Boat, Train, Airplane };
typedef kind_tuple<vehicle, kind::Car, kind::Bus, kind::Boat, kind::Train, kind::Airplane> kinds;
private: [snip] kind the_kind;
public: vehicle(kind k) : the_kind(k) {} [snip]
}; [snip]
Could you not use boost::variant, where the variant which function would return the tag, which is what the above vehicle::the_kind is, in essence: ------------ #include <boost/variant.hpp> enum kind { Car, Bus, Boat, Train, Airplane }; template < kind Kind
class vehicle_kind { }; typedef boost::variant < vehicle_kind<Car> , vehicle_kind<Bus> , vehicle_kind<Boat> , vehicle_kind<Train> , vehicle_kind<Airplane>
vehicle ; ------------ Then couldn't boost::variant library's static_visitor's do the dispatching? -regards, Larry

[Note: I don't know why my e-mail address got mangled. It should be "greened@..."] Larry Evans <cppljevans@suddenlink.net> writes:
Could you not use boost::variant
Thanks for your feedback! boost::variant can do some similar things but I think it is really aimed at a different problem. As I understand it, boost::variant's primary use case is to function as a type safe generalized union. Thus it has to deal with things like alignment, etc. that the presented technique (which I call "value dispatch" for lack of a better name) does not. A quick glance at boost::variant source code shows that it is quite complex. IMHO, value dispatch is much simpler. I was aiming for a value-centered analogue to virtual functions as opposed to an enhanced union type. boost::variant feels opaque to me (get<>, etc.) while value dispatch seems more natural and transparent for the use cases I had in mind. Note also that boost::variant appears to require or at least encourage inheritance (e.g. static_visitor) while value dispatch does not. I don't think that's a strong positive or negative as long as value semantics are maintained. That said, I'll now explain how I think inheritance might be useful with value dispatch. :) I think it should be possible to inherit from vehicle and/or visitor<vehicle ...> to allow new behaviors for derived types while maintaining value semantics. The trick is to figure out how to re-use, for example, the vehicle::drive_impl and vehicle::visit_dispatch routines without slicing. Making the object type a type parameter might help. It is an interesting problem but I haven't really looked at it yet. A not-currently-existing C++ feature would also be helpful in avoiding code duplication: enum class my_kind : vehicle::kind { // Inherit vehicle::kind names and values Submarine, Tractor }; Maybe all this isn't compelling over boost::variant but I've found it quite useful in a large project. -Dave

On 08/15/12 20:34, greened@obbligato.org wrote:
[Note: I don't know why my e-mail address got mangled. It should be "greened@..."]
Larry Evans <cppljevans@suddenlink.net> writes:
Could you not use boost::variant
Thanks for your feedback!
boost::variant can do some similar things but I think it is really aimed at a different problem. As I understand it, boost::variant's primary use case is to function as a type safe generalized union. Thus it has to deal with things like alignment, etc. that the presented technique (which I call "value dispatch" for lack of a better name) does not.
But the only reason why value dispatch does not have to deal with alignment is because the values are all empty. The only member values in vehicle is the tag: kind the_kind; As soon as you have to add other member variables (I assume a real Boat or a real Plane would have other member variables), you'll have to deal with alignment, if you want to save space.
A quick glance at boost::variant source code shows that it is quite complex. IMHO, value dispatch is much simpler.
Agreed. [snip]

Larry Evans <cppljevans@suddenlink.net> writes:
But the only reason why value dispatch does not have to deal with alignment is because the values are all empty. The only member values in vehicle is the tag:
kind the_kind;
As soon as you have to add other member variables (I assume a real Boat or a real Plane would have other member variables), you'll have to deal with alignment, if you want to save space.
I wrote value dispatch precisely for the case where one doesn't have to save space. The particular project I'm using this for previously used inheritance and virtual functions pretty much for the dispatch/compiler generated switch stuff. They all share identical (template) implementations and all had the same size. Even so, nothing would prevent a plan ol' union in the classes, right? You'll even be dispatched to a function that knows exactly which member is the right one. -Dave

AMDG On 08/15/2012 06:34 PM, greened@obbligato.org wrote:
I was aiming for a value-centered analogue to virtual functions as opposed to an enhanced union type.
Well, what you have is essentially a union type, even if that's not what you intended.
boost::variant feels opaque to me (get<>, etc.) while value dispatch seems more natural and transparent for the use cases I had in mind.
You should avoid using get<> on a variant. Whenever possible, apply_visitor is better.
Note also that boost::variant appears to require or at least encourage inheritance (e.g. static_visitor) while value dispatch does not. I don't think that's a strong positive or negative as long as value semantics are maintained.
Boost.Variant doesn't care about inheritance. static_visitor is like std::iterator or std::binary_function. It's just a helper that provides a result_type typedef. Anyway, static_visitor is for apply_visitor and doesn't have anything to do with the types stored in the variant. In Christ, Steven Watanabe

Steven Watanabe <watanabesj@gmail.com> writes:
AMDG
On 08/15/2012 06:34 PM, greened@obbligato.org wrote:
I was aiming for a value-centered analogue to virtual functions as opposed to an enhanced union type.
Well, what you have is essentially a union type, even if that's not what you intended.
It entirely depends on how the classes arrange member data, no? In my "real" project, the classes all have std::vectors with different kinds of stuff in them, depending on the kind tag. The elements all use the same value dispatch mechanism to implement polymorphism. In that sense I guess it's sort of like a union, but certainly not in the traditional sense.
boost::variant feels opaque to me (get<>, etc.) while value dispatch seems more natural and transparent for the use cases I had in mind.
You should avoid using get<> on a variant. Whenever possible, apply_visitor is better.
That's no less opaque.
Note also that boost::variant appears to require or at least encourage inheritance (e.g. static_visitor) while value dispatch does not. I don't think that's a strong positive or negative as long as value semantics are maintained.
Boost.Variant doesn't care about inheritance. static_visitor is like std::iterator or std::binary_function. It's just a helper that provides a result_type typedef. Anyway, static_visitor is for apply_visitor and doesn't have anything to do with the types stored in the variant.
That's why I said it's not a strong positive or negative. :) But I guess I'm sensing value dispatch isn't too interesting outside my own project. That's ok, it's made my life easier anyway and I found the lookup table implementation neat enough to share. :) -Dave
participants (4)
-
*smgreened@obbligato.org
-
greened@obbligato.org
-
Larry Evans
-
Steven Watanabe