Re: [boost] [Boost-users] formal review of Switch library ends tomorrow (Wednesday, January 9th) - reviews needed

On Jan 10, 2008 7:58 PM, Joel de Guzman <joel@boost-consulting.com> wrote:
Tobias Schwinger wrote:
Joel de Guzman wrote:
Single function:
I'm a strong advocate of smaller is better. Modularity matters. Big classes (or in this case function objects), metaprogram blobs (i.e. traits), humongous enums, and all such sort of dinosours :-) are best avoided. They are convenient up to a certain extent and becomes unmanageable beyond a certain limit.
In all of my use cases, I have N functions that are provided elsewhere and I have no control over (e.g. parser functions). I insist that this is the more common use case. Grouping them into a single big struct is an unnecessary and cumbersome step.
Still, if people insist, I outlined a way to convert the big function object to smaller 'bound' objects, in another post. Just bind 'em into smaller function chunks.
I think it's a misconception to assume that a single function object will be inherently big.
It's not about "big". It's about modular vs. monolithic. The best designs, IMO, are modular.
It's just another degree of freedom (namely what to look up) exposed to the user. Whether one can appreciate it seems a matter of taste to me...
A ---> B: function := L(I): functions[I]() B ---> A: transform(cases, L(I): make_pair<I>(bind(function,I())))
...and I happen to prefer the first transform.
I guess that reasoning is due to your thinking that the one monolithic automation functor is more common. That's where I disagree, vehemently(!). I've got lots of use cases that show otherwise. I assert that the more common case is to have as input N functions (member/free function pointers, binded functions, boost.functions, etc) and the switch_ is used to dispatch.
Let me emphasize this to the strongest extent: A single monolithic function for the cases in a switch facility is simply and utterly wrong, wrong and wrong! :-P If the design stands as it is, I better write my own. But it won't get my vote.
FWIW, I have a use case in which a single function is used for all the cases, but it doesn't seem monolithic. Structurally it is very similar to the example given in the proposed library's documentation - the case function looks something like this: // get_port_c behaves similarly to fusion::at_c // port_t<T> is a run-time polymorphic class derived from port // the goal of this whole thing is to return a port * (port is the base class of a run-time polymorphic class hierarchy) from an object of some type T (T is required to model a certain concept - Port) template<typename Component> struct get_port_case { typedef void result_type; template<class Case> void operator()(Case) const { ret.reset(new port_t< typename result_of::get_port_c< Component, Case::value, >::type >(get_port_c<Case::value>(c))); } get_port_case(Component& c, std::auto_ptr<port> &ret) : c(c), ret(ret) {} Component &c; std::auto_ptr<port> &ret; }; It is used like this (the number of ports is known at compile time but not at "coding time"): template<typename Component> std::auto_ptr<port> get_port(Component &c, int port_num) { std::auto_ptr<port> ret; typedef mpl::range_c< int, 0, mpl::size< typename traits_of<Component>::type::ports> ::value> range; boost::switch_<range>(port_num, detail::get_port_case<Component>(c, ret)); return ret; } "A" seems ideal for this use case. I have trouble seeing how to use "B" for it (easily) without making it so that the index is passed to the function object and allowing something like case<range>(...). But I might not be seeing all the possibilities. How can I implement this using "B"? Regardless of this example, if there isn't a "one size fits all" interface that can be implemented in a lightweight enough fashion to satisfy everyone, * should the Switch library provide both interfaces in separate functions (even if they are both implemented with their own PP)?; or * maybe the submitted library should have a different name/more clearly stated scope, as Tobias has suggested? Kudos to all of you for trying so hard at finding a solution for this! Stjepan

Stjepan Rajko wrote:
FWIW, I have a use case in which a single function is used for all the cases, but it doesn't seem monolithic. Structurally it is very similar to the example given in the proposed library's documentation - the case function looks something like this:
// get_port_c behaves similarly to fusion::at_c // port_t<T> is a run-time polymorphic class derived from port // the goal of this whole thing is to return a port * (port is the base class of a run-time polymorphic class hierarchy) from an object of some type T (T is required to model a certain concept - Port)
(snip use case similar to tutorial)
"A" seems ideal for this use case. I have trouble seeing how to use "B" for it (easily) without making it so that the index is passed to the function object and allowing something like case<range>(...). But I might not be seeing all the possibilities. How can I implement this using "B"?
Ok, you are right. There is indeed no way to "bind" the case (a compile time constant (e.g. mpl::int_)). It is indeed crucial to have the case passed as the argument. Once we have it, then (I'm inventing some more syntax): switch_<RT>(n)(all_cases(f)); Come to think of it, this is the only solution needed to map an A-style all-in-one-function to B! No need for Fusion, no need for extra infrastructure. All we need to do is pass the case as an argument to f. The key point (and one that I am trying to capture) is to define the Concept of a Case. That is crucial. Once that's done, everything flows naturally. ------------------- Aside: In addition to my suggested multiple case handling: case_<N, O, P, Q...>(f); yes, perhaps it's also good to have a case range: case_range<From, To>(f); ------------------- Regards, -- Joel de Guzman http://www.boost-consulting.com http://spirit.sf.net

Joel de Guzman wrote:
Stjepan Rajko wrote:
"A" seems ideal for this use case. I have trouble seeing how to use "B" for it (easily) without making it so that the index is passed to the function object and allowing something like case<range>(...). But I might not be seeing all the possibilities. How can I implement this using "B"?
Ok, you are right. There is indeed no way to "bind" the case (a compile time constant (e.g. mpl::int_)). It is indeed crucial to have the case passed as the argument. Once we have it, then (I'm inventing some more syntax):
switch_<RT>(n)(all_cases(f));
Heh, that won't work. You really need to supply the cases: switch_<RT>(n)(case_range<From, To>(f));
Come to think of it, this is the only solution needed to map an A-style all-in-one-function to B! No need for Fusion, no need for extra infrastructure. All we need to do is pass the case as an argument to f.
The key point (and one that I am trying to capture) is to define the Concept of a Case. That is crucial. Once that's done, everything flows naturally.
I'll try to come up with a complete interface proposal that will encompass all use cases. It will be lightweight (no need for extra infrastructure) and efficient. I believe it can be done. Regards, -- Joel de Guzman http://www.boost-consulting.com http://spirit.sf.net

Joel de Guzman wrote:
I'll try to come up with a complete interface proposal that will encompass all use cases. It will be lightweight (no need for extra infrastructure) and efficient. I believe it can be done.
Ok, here's what I have for draft of the Case concept. I believe this is most crucial in the design of the switch_ as I proposed in a recent post. Comments, feedback, suggestions welcome. Case Concept: Specialization of unary Polymorphic Function Object that, in addition to the requirements defined in Polymorphic Function Object, encodes a list of labels in its type necessary for switch dispatch. Notation: c, c0, c1, ... cN Case objects C A Case type I An MPL Integral Constant type i An MPL Integral Constant object N, First, Last Integral constants N0, N1, ...NN A list of integral constants f A unary function case_c, case_range_c, case_, default Case factories Valid Expressions: Expression Semantics ---------- -------------------------------------------------------- C::labels An MPL Sequence of Integral Constants c(i) Function application case_<I>(f) Returns a Case object with supplied MPL Sequence of Integral Constants. forwarding to the encapsulated function object f. case_c<N>(f) Returns a Case object with a single label N forwarding to the encapsulated function object f. Equivalent to: case_<mpl::vector_c<int, N> >(f) case_c< N0, N1, ...NN>(f) Returns a Case object with a list of labels forwarding to the encapsulated function object f. Equivalent to: case_<mpl::vector_c< int, N0, N1, ...NN> >(f) case_range_c< First, Last>(f) Returns a Case object with a range of labels forwarding to the encapsulated function object f. Equivalent to: case_<mpl::range_c< int, First, Last> >(f) default_(f) Returns a Case object with an empty labels list. forwarding to the encapsulated function object f. c0, c1, ... cN Returns a fusion sequence of Case objects Notes: * While c0, c1, ... cN returns a fusion sequence, it does not have to use Fusion. It can be a very simple cons list of references to the actual arguments similar to the zero overhead Proto expression tree. It must be compatible with Fusion though, but that can be abstracted away. * case_c and case_range_c are simple shortcuts. The type of integral constant is int. The actual type can be casted to the actual switch parameter. I think this is safe, but a variation is to have case_c and case_range_c expect the actual type (like mpl::vector_c and mpl::range_c.). E.g.: case_c<int, N>. * case_<I>(f) is equivalent to the original Steven interface. Regards, -- Joel de Guzman http://www.boost-consulting.com http://spirit.sf.net

Joel de Guzman wrote:
Joel de Guzman wrote:
I'll try to come up with a complete interface proposal that will encompass all use cases. It will be lightweight (no need for extra infrastructure) and efficient. I believe it can be done.
Ok, here's what I have for draft of the Case concept. I believe this is most crucial in the design of the switch_ as I proposed in a recent post. Comments, feedback, suggestions welcome.
AFAICT it looks very good. Here come some more thoughts:
Case Concept: Specialization of unary Polymorphic Function Object that, in addition to the requirements defined in Polymorphic Function Object, encodes a list of labels in its type necessary for switch dispatch.
Notation:
c, c0, c1, ... cN Case objects C A Case type I An MPL Integral Constant type i An MPL Integral Constant object N, First, Last Integral constants N0, N1, ...NN A list of integral constants f A unary function case_c, case_range_c, case_, default Case factories
Valid Expressions:
Expression Semantics ---------- -------------------------------------------------------- C::labels An MPL Sequence of Integral Constants
c(i) Function application
seemingly equivalent to c(I()) Function application (note: constant encoded in type). Maybe should be c.template apply<ResultType>(I()) instead (note: Passing in the result type).
case_<I>(f) Returns a Case object with supplied MPL Sequence of Integral Constants. forwarding to the encapsulated function object f.
I think this one should be called 'cases' and 'case_' should take a single MPL constant, for consistency.
case_c<N>(f) Returns a Case object with a single label N forwarding to the encapsulated function object f. Equivalent to: case_<mpl::vector_c<int, N> >(f) case_c< N0, N1, ...NN>(f) Returns a Case object with a list of labels forwarding to the encapsulated function object f. Equivalent to: case_<mpl::vector_c< int, N0, N1, ...NN> >(f) case_range_c< First, Last>(f) Returns a Case object with a range of labels forwarding to the encapsulated function object f. Equivalent to: case_<mpl::range_c< int, First, Last> >(f)
default_(f) Returns a Case object with an empty labels list. forwarding to the encapsulated function object f.
c0, c1, ... cN Returns a fusion sequence of Case objects
seemingly equivalent to (c0, c1, ... cN) (note: Application of comma operator ;-)). ?! Why doesn't it just return another (compound) Case object and leave the Sequence an unspecified implementation detail...
Notes:
* While c0, c1, ... cN returns a fusion sequence, it does not have to use Fusion. It can be a very simple cons list of references to the actual arguments similar to the zero overhead Proto expression tree. It must be compatible with Fusion though, but that can be abstracted away.
...this way we'd have that abstraction specified, already.
* case_c and case_range_c are simple shortcuts. The type of integral constant is int. The actual type can be casted to the actual switch parameter. I think this is safe, but a variation is to have case_c and case_range_c expect the actual type (like mpl::vector_c and mpl::range_c.). E.g.: case_c<int, N>.
I prefer the former.
* case_<I>(f) is equivalent to the original Steven interface.
We could have this case swallow the index during function invocation, as for manual application we probably don't want to have it. I think it's typical and easy enough to just model the Case concept directly... Regards, Tobias

AMDG Tobias Schwinger wrote:
Joel de Guzman wrote:
c0, c1, ... cN Returns a fusion sequence of Case objects
seemingly equivalent to
(c0, c1, ... cN)
(note: Application of comma operator ;-)).
?! Why doesn't it just return another (compound) Case object and leave the Sequence an unspecified implementation detail...
So you want switch_ to be template<class R, class I, class Cases> requires Case<Cases> && BuiltInIntegerOrEnum<I> R switch(I i, Cases cases); IMO, There is a very big problem here. Suppose that you have a fusion sequence that holds your cases and function objects coming from somewhere else. The problem of adapting it to switch_ is no easier than it would be with my original interface. In Christ, Steven Watanabe

Steven Watanabe wrote:
AMDG
Tobias Schwinger wrote:
Joel de Guzman wrote:
c0, c1, ... cN Returns a fusion sequence of Case objects
seemingly equivalent to
(c0, c1, ... cN)
(note: Application of comma operator ;-)).
?! Why doesn't it just return another (compound) Case object and leave the Sequence an unspecified implementation detail...
So you want switch_ to be
template<class R, class I, class Cases> requires Case<Cases> && BuiltInIntegerOrEnum<I> R switch(I i, Cases cases);
IMO, There is a very big problem here. Suppose that you have a fusion sequence that holds your cases and function objects coming from somewhere else. The problem of adapting it to switch_ is no easier than it would be with my original interface.
Oh man, either my headache is killing me, or I am currently in dumb mode. I can't understand both Tobias and Steven. What's a "compound Case object"? Regards, -- Joel de Guzman http://www.boost-consulting.com http://spirit.sf.net

Joel de Guzman wrote:
Steven Watanabe wrote:
AMDG
Tobias Schwinger wrote:
Joel de Guzman wrote:
c0, c1, ... cN Returns a fusion sequence of Case objects
seemingly equivalent to
(c0, c1, ... cN)
(note: Application of comma operator ;-)).
?! Why doesn't it just return another (compound) Case object and leave the Sequence an unspecified implementation detail...
So you want switch_ to be
template<class R, class I, class Cases> requires Case<Cases> && BuiltInIntegerOrEnum<I> R switch(I i, Cases cases);
IMO, There is a very big problem here. Suppose that you have a fusion sequence that holds your cases and function objects coming from somewhere else. The problem of adapting it to switch_ is no easier than it would be with my original interface.
Oh man, either my headache is killing me, or I am currently in dumb mode. I can't understand both Tobias and Steven. What's a "compound Case object"?
(c0,c1,...,cN) being just another 'Case' rather than a 'Sequence'. Regards, Tobias

Tobias Schwinger wrote:
Joel de Guzman wrote:
Joel de Guzman wrote:
I'll try to come up with a complete interface proposal that will encompass all use cases. It will be lightweight (no need for extra infrastructure) and efficient. I believe it can be done. Ok, here's what I have for draft of the Case concept. I believe this is most crucial in the design of the switch_ as I proposed in a recent post. Comments, feedback, suggestions welcome.
AFAICT it looks very good. Here come some more thoughts:
Thank you! Appreciated. I also have some tweaks of my own. I'll see if I can post an update from yours and Steven's comments.
Case Concept: Specialization of unary Polymorphic Function Object that, in addition to the requirements defined in Polymorphic Function Object, encodes a list of labels in its type necessary for switch dispatch.
Notation:
c, c0, c1, ... cN Case objects C A Case type I An MPL Integral Constant type
Add: SI An MPL Sequence of MPL Integral Constants
i An MPL Integral Constant object N, First, Last Integral constants N0, N1, ...NN A list of integral constants f A unary function case_c, case_range_c, case_, default Case factories
Valid Expressions:
Expression Semantics ---------- -------------------------------------------------------- C::labels An MPL Sequence of Integral Constants
c(i) Function application
seemingly equivalent to
c(I()) Function application
(note: constant encoded in type). Maybe should be
c.template apply<ResultType>(I())
instead (note: Passing in the result type).
case_<I>(f) Returns a Case object with supplied MPL Sequence of Integral Constants. forwarding to the encapsulated function object f.
I think this one should be called 'cases' and 'case_' should take a single MPL constant, for consistency.
Good point. Fixes: case_<I>(f) Returns a Case object with supplied Integral Constants. forwarding to the encapsulated function object f. cases<SI>(f) Returns a Case object with supplied MPL Sequence of Integral Constants. forwarding to the encapsulated function object f.
case_c<N>(f) Returns a Case object with a single label N forwarding to the encapsulated function object f. Equivalent to: case_<mpl::vector_c<int, N> >(f) case_c< N0, N1, ...NN>(f) Returns a Case object with a list of labels forwarding to the encapsulated function object f. Equivalent to: case_<mpl::vector_c< int, N0, N1, ...NN> >(f)
Following your logic, shouldn't this be: cases_c< N0, N1, ...NN>(f) Returns a Case object with a list of labels forwarding to the encapsulated function object f. Equivalent to: case_<mpl::vector_c< int, N0, N1, ...NN> >(f) ?
case_range_c< First, Last>(f) Returns a Case object with a range of labels forwarding to the encapsulated function object f. Equivalent to: case_<mpl::range_c< int, First, Last> >(f)
default_(f) Returns a Case object with an empty labels list. forwarding to the encapsulated function object f.
c0, c1, ... cN Returns a fusion sequence of Case objects
seemingly equivalent to
(c0, c1, ... cN)
(note: Application of comma operator ;-)).
?! Why doesn't it just return another (compound) Case object and leave the Sequence an unspecified implementation detail...
I'll reply to this later along with Steven's other post.
Notes:
* While c0, c1, ... cN returns a fusion sequence, it does not have to use Fusion. It can be a very simple cons list of references to the actual arguments similar to the zero overhead Proto expression tree. It must be compatible with Fusion though, but that can be abstracted away.
...this way we'd have that abstraction specified, already.
Yes. Haven't tried, but perhaps we can do it with only forward declarations of Fusion's extension protocol, and without having to include any of Fusion? Is that possible?
* case_c and case_range_c are simple shortcuts. The type of integral constant is int. The actual type can be casted to the actual switch parameter. I think this is safe, but a variation is to have case_c and case_range_c expect the actual type (like mpl::vector_c and mpl::range_c.). E.g.: case_c<int, N>.
I prefer the former.
Same here.
* case_<I>(f) is equivalent to the original Steven interface.
Change: * cases<SI>(f) is equivalent to the original Steven interface.
We could have this case swallow the index during function invocation, as for manual application we probably don't want to have it. I think it's typical and easy enough to just model the Case concept directly...
Forgive me, I think my headache is getting in the way and I can't seem to parse this sentence properly. Could you please explain a bit more? Regards, -- Joel de Guzman http://www.boost-consulting.com http://spirit.sf.net

Joel de Guzman wrote:
I'll see if I can post an update from yours and Steven's comments.
Ok, here's the update: Case Concept: Specialization of unary Polymorphic Function Object that, in addition to the requirements defined in Polymorphic Function Object, encodes a label in its type necessary for switch dispatch. Notation: c, c0, c1, ... cN Case objects C A Case type I An MPL Integral Constant type SI An MPL Sequence of Integral Constant types i An MPL Integral Constant object N, First, Last Integral constants N0, N1, ...NN A list of integral constants f A unary function cases_c, case_c, case_range_c, case_, cases, default Case factories Valid Expressions: Expression Semantics ---------- --------------------------- C::label An MPL Integral Constant or none (none type is used to signal the default case). c(i) Function application. Equivalent to c(I()). case_<I>(f) Returns a single element fusion sequence containing a Case object with supplied MPL Integral Constant, forwarding to the encapsulated function object f. cases<SI>(f) Returns a fusion sequence of Case objects with labels corresponding to each element of the supplied MPL Sequence of Integral Constants, forwarding to the encapsulated function object f. case_c<N>(f) Returns a single element fusion sequence containing a Case object with supplied label N, forwarding to the encapsulated function object f. Equivalent to: case_<mpl::int_<N> >(f). cases_c< N0, N1, ...NN>(f) Returns a fusion sequence of Case objects with labels N0, N1, ...NN, forwarding to the encapsulated function object f. Equivalent to: cases<mpl::vector_c<int, N0, N1, ...NN> >(f) case_range_c< First, Last>(f) Returns a fusion sequence of Case objects with labels mpl::int_<First> ... mpl::int_<Last>, forwarding to the encapsulated function object f. Equivalent to: cases<mpl::range_c<int, First, Last> >(f) default_(f) Returns a single element fusion sequence containing a Case object with a none label, forwarding to the encapsulated function object f. c0, c1, ... cN Returns a fusion sequence of Case objects. Notes: * While c0, c1, ... cN returns a fusion sequence, it does not have to use Fusion. It can be a very simple cons list of references to the actual arguments similar to the zero overhead Proto expression tree. It must be compatible with Fusion though, but that can be abstracted away. * case_c, cases_c and case_range_c are simple shortcuts. The type of integral constant is int. The actual type can be casted to the actual switch parameter. Since the value is known at compile time the cast can be checked at compile time to detect overflow. * cases<SI>(f) is equivalent to the original Steven interface. Regards, -- Joel de Guzman http://www.boost-consulting.com http://spirit.sf.net

Joel de Guzman wrote:
Tobias Schwinger wrote:
(c0, c1, ... cN)
(note: Application of comma operator ;-)).
?! Why doesn't it just return another (compound) Case object and leave the Sequence an unspecified implementation detail...
<...>
Same here.
* case_<I>(f) is equivalent to the original Steven interface.
Change:
* cases<SI>(f) is equivalent to the original Steven interface.
We could have this case swallow the index during function invocation, as for manual application we probably don't want to have it. I think it's typical and easy enough to just model the Case concept directly...
Forgive me, I think my headache is getting in the way and I can't seem to parse this sentence properly. Could you please explain a bit more?
That sentence is a bit unclear, too. I meant: Not forwarding the index. If 'switch_' takes a Case Object for the cases, it might be easy enough to just implement that concept rather than using generators. Get well! Regards, Tobias

AMDG Joel de Guzman wrote:
Valid Expressions:
Expression Semantics ---------- -------------------------------------------------------- C::labels An MPL Sequence of Integral Constants
I think that a case should only have a single label. Allowing multiple labels makes the implementation harder without adding any significant utility. See below.
case_<I>(f) Returns a Case object with supplied <snip more generators>
These can return fusion sequences of cases instead of plain cases. This has the additional benefit that switch_ can be specified as taking a fusion sequence of cases, without having to handle a single case specially.
default_(f) Returns a Case object with an empty labels list. forwarding to the encapsulated function object f.
From the standpoint of the implementor of switch_, how do I figure out which Case is the default?
* case_c and case_range_c are simple shortcuts. The type of integral constant is int. The actual type can be casted to the actual switch parameter. I think this is safe
Since the value is known at compile time the cast can be checked at compile time to detect overflow. In Christ, Steven Watanabe

Steven Watanabe wrote:
AMDG
Joel de Guzman wrote:
Valid Expressions:
Expression Semantics ---------- -------------------------------------------------------- C::labels An MPL Sequence of Integral Constants
I think that a case should only have a single label. Allowing multiple labels makes the implementation harder without adding any significant utility. See below.
Hmmm... Good point. So, we basically break a Case with one or more labels to atomic parts? Nice.
case_<I>(f) Returns a Case object with supplied <snip more generators>
These can return fusion sequences of cases instead of plain cases. This has the additional benefit that switch_ can be specified as taking a fusion sequence of cases, without having to handle a single case specially.
Right! Makes sense. But wouldn't that use more compile time resources? Well, you know better :-)
default_(f) Returns a Case object with an empty labels list. forwarding to the encapsulated function object f.
From the standpoint of the implementor of switch_, how do I figure out which Case is the default?
I thought it would be just a check: mpl::empty<C::labels> But if you want single labels for each Case, then, perhaps: is_same<C::label, none> or somesuch. Am I missing something?
* case_c and case_range_c are simple shortcuts. The type of integral constant is int. The actual type can be casted to the actual switch parameter. I think this is safe
Since the value is known at compile time the cast can be checked at compile time to detect overflow.
Oh, so, you are also in favor of the former? So we all are. Regards, -- Joel de Guzman http://www.boost-consulting.com http://spirit.sf.net

AMDG Joel de Guzman wrote:
Steven Watanabe wrote:
case_<I>(f) Returns a Case object with supplied <snip more generators>
These can return fusion sequences of cases instead of plain cases. This has the additional benefit that switch_ can be specified as taking a fusion sequence of cases, without having to handle a single case specially.
Right! Makes sense. But wouldn't that use more compile time resources? Well, you know better :-)
I don't think so. I'd have to flatten the sequence anyway. This way you don't pay for it if you're not using it.
From the standpoint of the implementor of switch_, how do I figure out which Case is the default?
I thought it would be just a check:
mpl::empty<C::labels>
But if you want single labels for each Case, then, perhaps:
is_same<C::label, none>
or somesuch. Am I missing something
Ok. I didn't like mpl::empty because of the following possibility: switch_<r>(n)(case_range_c<0, N>(f), default(f)); If N is zero there's a problem. In Christ, Steven Watanabe
participants (4)
-
Joel de Guzman
-
Steven Watanabe
-
Stjepan Rajko
-
Tobias Schwinger