[result_of] decltype and function objects

I've noticed test failures when compiling some of boost in C++0x mode due to the change to boost::result_of to use decltype. The problem comes from function objects like this: template<class T> struct identity { typedef T result_type; T const & // <== problem here operator()(T const &t) const { return t; } }; In this (overly simplistic) example, we just want to return what is passed in. The computed type (result_type) is different than the return type of operator() ... and for a good (IMO) reason: to avoid an unnecessary copy in some cases. The idea is to accommodate usage like this: result_of<identity(BigObj)>::type i = identity()(BigObj()); It is really very important that decltype(i) be BigObj, and not BogObj const &, which would cause a dangling reference. But with the change to use decltype in boost::result_of, the dangling reference is exactly what I would get. Am I now forced to change identity::operator() return by value here? That seems sub-optimal to me. In many other potential uses of identity (e.g., when forwarding the result to some other function), the copy is totally unnecessary. -- Eric Niebler BoostPro Computing http://www.boostpro.com

On Thu, Nov 13, 2008 at 7:43 PM, Eric Niebler <eric@boost-consulting.com> wrote:
I've noticed test failures when compiling some of boost in C++0x mode due to the change to boost::result_of to use decltype. The problem comes from function objects like this:
template<class T> struct identity { typedef T result_type;
T const & // <== problem here operator()(T const &t) const { return t; } };
In this (overly simplistic) example, we just want to return what is passed in. The computed type (result_type) is different than the return type of operator() ...
Not actually, no. For awhile now, the draft C++0x standard has specified the computed or deduced std::result_of<F()>::type to be the result type of a call to the function object F. A simple way to implement C++0x std::result_of is using decltype. This is a great improvement over the old intrusive protocol requiring function objects to advertise (or falsely advertise ;) their return types. On platforms that support decltype, boost::result_of behaves like the proposed draft standard std::result_of. Actually, it may be the only/first implementation...
and for a good (IMO) reason: to avoid an unnecessary copy in some cases. The idea is to accommodate usage like this:
result_of<identity(BigObj)>::type i = identity()(BigObj());
For completeness, the above line won't compile unless you include BigObj as a template parameter to identity. So the line should be result_of<identity<BigObj>(BigObj)>::type i = identity<BigObj>()(BigObj()); However, that's not actually what you want. You're not interested in the return type of identity, you're interested in the type of the template argument. With C++0x std::identity, for example, the template argument type is std::identity::type. So you could write: std::identity<BigObj>::type i = std::identity<BigObj>()(BigObj()); Or obviously, in this particular situation, you could just write: BigObj i = identity<BigObj>()(BigObj());
It is really very important that decltype(i) be BigObj, and not BogObj const &, which would cause a dangling reference. But with the change to use decltype in boost::result_of, the dangling reference is exactly what I would get.
Am I now forced to change identity::operator() return by value here?
No. In general, if you want to remove references from the result type of any arbitrary callable object F, you can write: remove_reference< result_of<F()>::type
::type i = F()();
This guarantees that i is not a dangling reference without requiring changes to F. Daniel Walker

on Thu Nov 13 2008, Eric Niebler <eric-AT-boost-consulting.com> wrote:
I've noticed test failures when compiling some of boost in C++0x mode due to the change to boost::result_of to use decltype. The problem comes from function objects like this:
template<class T> struct identity { typedef T result_type;
T const & // <== problem here operator()(T const &t) const { return t; } };
In this (overly simplistic) example, we just want to return what is passed in. The computed type (result_type) is different than the return type of operator() ... and for a good (IMO) reason: to avoid an unnecessary copy in some cases. The idea is to accommodate usage like this:
result_of<identity(BigObj)>::type i = identity()(BigObj()); You need a template argument here---------------^
It is really very important that decltype(i) be BigObj, and not BogObj const &, which would cause a dangling reference. But with the change to use decltype in boost::result_of, the dangling reference is exactly what I would get.
It seems like you're expecting the line above to act like auto i = identity<BigObj>()(BigObj()); but that's not what result_of is supposed to do. It is supposed to tells you, in excruciating detail, about the return type of the function call.
Am I now forced to change identity::operator() return by value here?
Either that or change your expectations about the right way to declare i.
That seems sub-optimal to me. In many other potential uses of identity (e.g., when forwarding the result to some other function), the copy is totally unnecessary.
Ah, well. The optimal safe way to write an identity function is of course template <class T> T identity(T x) { return boost::move(x); } which on any decent compiler should cause no copies provided T is move-enabled, and only one if it isn't. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Delayed response, sorry... David Abrahams wrote:
Ah, well. The optimal safe way to write an identity function is of course
template <class T> T identity(T x) { return boost::move(x); }
which on any decent compiler should cause no copies provided T is move-enabled, and only one if it isn't.
What is boost::move() and where in boost does it live? -- Eric Niebler BoostPro Computing http://www.boostpro.com
participants (3)
-
Daniel Walker
-
David Abrahams
-
Eric Niebler