|
Boost : |
From: Eric Niebler (eric_at_[hidden])
Date: 2008-04-05 18:21:13
Peter Dimov wrote:
> Eric Niebler:
>
>> I clearly should have fleshed this out a bit more. Consider something
>> like a fusion::make_tuple function object
>>
>> struct make_tuple
>> {
>> template<typename Sig> struct result;
>>
>> template<typename This, typename Arg>
>> struct result< This( Arg ) >
>> {
>> typedef tuple< Arg > type;
>> };
>>
>> // This is wrong!
>> template<typename Arg>
>> typename result< make_tuple( Arg const & ) >::type
>> operator ()( Arg const &arg )
>> {
>> return tuple< Arg const & >( arg );
>> }
>> };
>>
>>
>> This is wrong because make_tuple(1) will cause the resulting tuple to
>> hold on to a dangling reference. But had it been called as:
>>
>> int const i = 0;
>> make_tuple( i );
>>
>> ... then it's correct.
>
> Yes. It's also wrong because result_of<make_tuple(int&)>::type says
> tuple<int&>, but the result of make_tuple( i ) is actually tuple<int
> const&>. But this is not a problem with result_of. It's not supposed to be
> used in this self-referential way. result_of<F(X)>::type is nothing more
> than an alias of decltype(f(x)),
Oh yes, that's very nice. :-) Can't wait for C++0x, but in the mean time ...
> where f and x have types F and X,
> respectively. So if you have:
>
> return f(x);
>
> you can write
>
> template<class X> typename result_of<F(X const&)>::type g( X const & x )
> {
> return f(x);
> }
>
> You don't use result_of<G(X)> in the return type of g(x). This would be the
> equivalent of using decltype(g(x)) as the return type of g.
>
> If you write the actual make_tuple:
>
> struct make_tuple
> {
> template<class X> tuple<X> operator()( X const & x ) const
> {
> return tuple<X>( x );
> }
>
> template<class X> tuple<X&>
> operator()( reference_wrapper<X> const & x ) const
> {
> return tuple<X&>( x.get() );
> }
> };
>
> you'll see that it's relatively easy to write a result_of specialization for
> it. The references are an annoyance, sure. But the idea is that you start
> with the function object and then derive its result_of, not vice versa.
>
> Maybe I'm missing something else.
Sure, in isolation this works, but let's look at how this composes. Here
is a template that takes two unary TR1 function object types and
composes them.
template<class F1, class F2>
struct compose
{
template<typename Sig>
struct result;
template<typename This, typename Arg>
struct result< This( Arg ) >
{
typedef typename result_of<
F2( typename result_of< F1(Arg) >::type )
>::type type;
};
template<typename Arg>
typename result_of<
F2( typename result_of< F1(Arg const &) >::type )
>::type
operator()( Arg const & arg )
{
return F2()(F1()(arg));
}
// And a non-const overload, too.
};
I think that's right so far. Now imagine a simple identity function
object, that just returns its argument unmodified:
struct identity
{
template<typename Sig>
struct result;
template<typename This, typename Arg>
struct result<This(Arg)>
{
typedef Arg type;
};
template<typename Arg>
Arg const &operator()(Arg const &arg)
{
return arg;
}
// and a non-const overload, too
};
Now take a look at what happens when I do this:
BigObject const obj;
compose< identity, make_tuple >()( obj );
(And assume that make_tuple is implemented as you implemented it above.)
Now it looks to me like the wrong thing is going to happen here. In the
return type calculation, make_tuple is being told it is going to get an
lvalue (BigObject const &). But the rvalue overload of
make_tuple::operator() is going to be selected, causing BigObject to be
copied when it shouldn't be -- if it even compiles at all.
So where have I screwed up my logic?
-- 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