[proto] design rationale for expr<proto::function, argsN<Fun, Args...> >?

Hi! I may have overlooked something, but while thinking about proto's design I was surprised by some asymmetry. I'd like to know why this was done the way it is. Consider two objects (type A and B are proto terminals) A a; B b; The expression a + b is considered equivalent to a function call operator+(a, b). The corresponding type representation approximately is expr<tag::binary_plus // I will vote for this ;-) ,args2<expr<tag::terminal, A> ,expr<tag::terminal, B> > > This is done according to the rationale that what binary_plus really means is domain specific. Therefore the expression is simply tagged by the name of the function call, while the second argument is the typelist containing the parameters in the same order as they appear in the expression. Important: any information about what the function call means is stored *outside* the expression. Up to here I appreciate the design and IMHO this is the most appropriate way to represent an expression (I said that before, I know). Now we introduce another function call - not an operator this time, but in C++ these are equivalent. struct fun_t {}; terminal<fun_t>::type const fun = {{}}; fun(a, b); This is represented by something similar to expr< tag::function ,args3< ref_<expr<tag::terminal, args0<fun_tag> > const> ,expr<tag::terminal, args0<> > ,expr<tag::terminal, args0<> > > > This is suprising. In contrast to operator+, the function fun is treated as if it was an *operand*, not an *operator*. The expression is not tagged by _the_ function call, but by a global this-is-a-function-tag and the function is stored in the argument typelist. What fun means is context dependent, I would expect the context to take care about what fun_t might mean, so if we want symmetry first I'd expect a type representation similar to expr< tag::function<fun_t> ,args2< ,expr<tag::terminal, args0<...ommitted...> > ,expr<tag::terminal, args0<...ommitted...> > >
So I wonder whether this asymmetry was introduced due to the fact that function objects could have a state which makes them an in-between between operator and operand ... Can we save state in tag::function<T> then? expr< tag::function<expr<tag::terminal, args0<fun_t> > > ,args2< ,expr<tag::terminal, args0<...ommitted...> > ,expr<tag::terminal, args0<...ommitted...> > >
I do not like that one either ... I ask because ripping the function off the argument list via fusion magic still looks odd to me and makes me feel uncomfortable: Any compile time algorithm has to fork for function calls if it is to apply an operation to the operands. Shed some light on this, please. Markus

AMDG Markus Werle wrote:
Now we introduce another function call - not an operator this time, but in C++ these are equivalent.
struct fun_t {}; terminal<fun_t>::type const fun = {{}};
fun(a, b);
This is represented by something similar to
expr< tag::function ,args3< ref_<expr<tag::terminal, args0<fun_tag> > const> ,expr<tag::terminal, args0<> > ,expr<tag::terminal, args0<> > >
This is suprising. In contrast to operator+, the function fun is treated as if it was an *operand*, not an *operator*. The expression is not tagged by _the_ function call, but by a global this-is-a-function-tag and the function is stored in the argument typelist.
I don't find it surprising. This behavior is exactly what I would expect. fun(a, b) is equivalent to fun.operator()(a, b). In a concept fun(int, int) will be represented as concept Callable<class T> { void operator()(T, int, int); }; if I recall correctly.
What fun means is context dependent, I would expect the context to take care about what fun_t might mean, so if we want symmetry first I'd expect a type representation similar to
expr< tag::function<fun_t> ,args2< ,expr<tag::terminal, args0<...ommitted...> > ,expr<tag::terminal, args0<...ommitted...> > >
So I wonder whether this asymmetry was introduced due to the fact that function objects could have a state which makes them an in-between between operator and operand ...
IMO, a function object is not an operator at all. The operator involved is the function call operator which takes a reference to the function object (*this) as the first parameter. I don't see this as being any more asymmetric than any other member function call. In Christ, Steven Watanabe

Steven Watanabe wrote:
AMDG
Markus Werle wrote:
Now we introduce another function call - not an operator this time, but in C++ these are equivalent.
struct fun_t {}; terminal<fun_t>::type const fun = {{}};
fun(a, b);
This is represented by something similar to
expr< tag::function ,args3< ref_<expr<tag::terminal, args0<fun_tag> > const> ,expr<tag::terminal, args0<> > ,expr<tag::terminal, args0<> > >
This is suprising. In contrast to operator+, the function fun is treated as if it was an *operand*, not an *operator*. The expression is not tagged by _the_ function call, but by a global this-is-a-function-tag and the function is stored in the argument typelist.
I don't find it surprising. This behavior is exactly what I would expect.
fun(a, b) is equivalent to fun.operator()(a, b). In a concept fun(int, int) will be represented as
concept Callable<class T> { void operator()(T, int, int); };
if I recall correctly.
Precisely. Another way to see this is to consider that the following might be a valid expression within a domain: (a+b)(a-b); That's equivalent to (a+b).operator()(a-b). Clearly, this needs to be encoded as a binary tree with (a+b) as the left child and (a-b) as the right, with tag::function. To see a real-world example that uses operator() this way, see: http://boost-sandbox.sourceforge.net/libs/proto/doc/html/boost_proto/users_g... It's an implementation of the map_list_of() function from the Boost.Assign library. It lets you initialize a map as follows: // Initialize a map: std::map<std::string, int> op = map_list_of ("<",1) ("<=",2) (">",3) (">=",4) ("=",5) ("<>",6) ;
What fun means is context dependent, I would expect the context to take care about what fun_t might mean, so if we want symmetry first I'd expect a type representation similar to
expr< tag::function<fun_t> ,args2< ,expr<tag::terminal, args0<...ommitted...> > ,expr<tag::terminal, args0<...ommitted...> > >
So I wonder whether this asymmetry was introduced due to the fact that function objects could have a state which makes them an in-between between operator and operand ...
IMO, a function object is not an operator at all. The operator involved is the function call operator which takes a reference to the function object (*this) as the first parameter. I don't see this as being any more asymmetric than any other member function call.
Right, it's perfectly symmetric with all the other operators. -- Eric Niebler Boost Consulting www.boost-consulting.com

On 03/22/08 22:18, Eric Niebler wrote:
Steven Watanabe wrote:
AMDG
Markus Werle wrote:
Now we introduce another function call - not an operator this time, but in C++ these are equivalent.
struct fun_t {}; terminal<fun_t>::type const fun = {{}};
fun(a, b);
This is represented by something similar to
expr< tag::function ,args3< ref_<expr<tag::terminal, args0<fun_tag> > const> ,expr<tag::terminal, args0<> > ,expr<tag::terminal, args0<> > >
This is suprising. In contrast to operator+, the function fun is treated as if it was an *operand*, not an *operator*. The expression is not tagged by _the_ function call, but by a global this-is-a-function-tag and the function is stored in the argument typelist.
I don't find it surprising. This behavior is exactly what I would expect.
fun(a, b) is equivalent to fun.operator()(a, b). In a concept fun(int, int) will be represented as
concept Callable<class T> { void operator()(T, int, int); };
if I recall correctly.
Precisely. Another way to see this is to consider that the following might be a valid expression within a domain:
(a+b)(a-b);
That's equivalent to (a+b).operator()(a-b). Clearly, this needs to be encoded as a binary tree with (a+b) as the left child and (a-b) as the right, with tag::function.
In mpl, this is called apply instead of function. Here: boost-trunk/libs/mpl/doc/refmanual/apply.html it appears as: template< typename F, typename A1,... typename An > struct applyn { typedef unspecified type; }; I think that using tag::apply instead of tag::function would make proto more consistent with other boost naming conventions, and also make it easier for others (who were familiar with mpl apply) to see that it's not surprising that "function fun is treated as if it were and *operand*". I, at first kinda agreed with Markus' assessment; however, now I see why it might be better as is. If I'd seen apply instead of function, this would have made the justification for the current form easier to see.
participants (4)
-
Eric Niebler
-
Larry Evans
-
Markus Werle
-
Steven Watanabe