
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