|
Boost : |
From: Eric Niebler (eric_at_[hidden])
Date: 2008-04-05 15:10:59
My experience developing Proto has taught me a hard lesson about the
ResultOf protocol that I thought I would share. Maybe someone here can
tell me if/where I've gone wrong.
There's a part of Proto (proto::call<>) that takes function objects,
passes arguments to them and returns the result. I use result_of to
compute the return type. Fine.
Proto defines a function object, make_foo, whose behavior depends on the
lvalue/rvalue-ness of its arguments. So for instance:
// Return type of make_foo when passed a rvalue
// int and an lvalue float:
result_of< make_foo( int, float & ) >::type
Like a good citizen, I've written make_foo's function call operator to
recognize reference_wrapped and non-const-ref arguments as representing
lvalues and all others as rvalues. (This is because C++03 doesn't have
rvalue references.) Fine.
Now, given some function type F (which may or may not be make_foo) and
some arguments a0...an (which may or may not be lvalues), how do I invoke F?
The strictly correct thing to do would be to wrap lvalues in
reference_wrappers before passing them to F. If I don't, make_foo will
do the wrong thing. However, Proto needs to work with function objects
in the wild, very few of which do the right thing with reference_wrapped
arguments, in my experience. What to do? I don't know.
This business of needing to reference_wrap objects to get correct
lvalue/rvalue semantics is a deal-breaker for Proto transforms. If I
needed to wrap each lvalue argument before forwarding it to the next
transform, and unpack each argument within each tranform, it would:
a) Make writing transforms extremely tedious.
b) Kill compile times with lots of unnecessary mpl::if_,
is_reference, reference_wrapper and unwrap_reference
instantiations.
For these reasons, I've decided that Proto's transforms will use a
different function object protocol. None of that will be visible from
the outside. Transforms will still look like TR1-style function objects.
But transforms will provide a separate interface for internal
consumption that doesn't have this rvalue/lvalue problem.
The problem, as I see it, stems from the fact that the return type
calculation is separated from the function invocation.
struct blarg
{
template<typename This, typename Arg>
struct result< This( Arg ) >
{
// OK, if Arg is a reference, then it's an lvalue!
};
template<typename Arg>
typename result< make_foo( ??? ) >::type
operator()( Arg const & arg )
{
// whoops, I don't know here if arg is an lvalue or rvalue
}
};
If function objects were instead written like this ...
struct blarg
{
template<typename Arg>
struct impl
{
// OK, if Arg is a reference, it's an lvalue
typedef ... result_type;
result_type operator()(typename add_const_ref<Arg>::type arg)
{
// The right thing can happen here.
}
};
};
... the problem goes away.
-- Eric Niebler Boost Consulting www.boost-consulting.com
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk