
On Thu, May 1, 2008 at 2:56 PM, David Abrahams <dave@boost-consulting.com> wrote:
on Tue Apr 29 2008, "Daniel Walker" <daniel.j.walker-AT-gmail.com> wrote:
On Tue, Apr 29, 2008 at 5:43 AM, David Abrahams <dave@boost-consulting.com> wrote:
The difficulty of implementing the result_of protocol has always bothered me. I've often found myself writing a primary result<...> template and several specializations to handle different cv-qualifications, and I've still not felt that I was doing it quite right.
Yes, I think that's the right way to do it. My understanding is that every time you write a new overload, you need to write a new specialization of result<> for that overload, whether you're overloading on arity, type, cv-qualification, etc.
Ah, but I think you miss the point. I need the specializations to handle all the different CV-qualifications of the *function object*. I suppose if you have full control over the function object you can do it with a nested result<>, and now I forget why I might not have wanted to do that. Perhaps I just didn't want to force F to be instantiated when checking its result type.
OK, I see. Actually, now that I think about it, I would revise what I wrote before. I said that you should write a result<> specialization for each overload, but that's not exactly correct. You only need to write result<> specializations for overloads that have different results. Obviously, if all the overloads have the same return type, you don't need the nested result<> at all; you can just define result_type. If the return type is argument dependent but doesn't change based on the cv-qualification of the function object, then you only need one result<> specialization for all the cv-qualification overloads. Something along the lines of the following should work fine: struct functor { template<class> struct result; template<class F, class T> struct result<F(T)> { typedef T type; }; template<class T> T operator()(T t) { return t; } template<class T> T operator()(T t) const { return t; } template<class T> T operator()(T t) volatile { return t; } }; functor f; result_of<functor(int)>::type x = f(0); const functor g = functor(); result_of<const functor(int)>::type y = g(0); volatile functor h; result_of<volatile functor(int)>::type z = h(0); Of course, if you don't have full control of the function object or you don't want to instantiate it, you can always specialize result_of directly. In fact, I know of one scenario where it's necessary to specialize result_of given the present heuristic: If a functor has argument dependent overloads for zero or more arguments, the nullary case can only be handled by specializing result_of, because the heuristic only checks result_type for nullary calls and never checks result<> if result_type is defined. This is a dark corner case where the heuristic fails horribly. However, now that the heuristic will never be codified in the standard, there's no reason not to look for better strategies on C++03 platforms.
<snip>
Is there an easier way?
Yes, I believe so. The result_of documentation says that for a callable object f of type F you have result_of<F()>::type. So, I think one should use the exact type in situations like this. In your example above, f is a F const&, so...
template <class F> typename result_of<F const&()>::type call(F const& f) { return f(); }
Fine, then take the case where call takes f by value; then F can be a function type and result_of<F()> is illegal.
Because function call operators have to be member functions in C++03, an lvalue function object must behave the same as an rvalue, so
result_of<T()>::type == result_of<T&()>::type
is an invariant. That means I can afford to always add a reference, but it's still painful:
typename result_of<typename boost::add_reference<F>::type ()>::type
But not when call() is passed a builtin function, and F is deduced as a pointer, right? So, I think that the problem occurs when passing functions as arguments, which later precipitates issues with result_of. As Peter observed a few weeks ago when Eric was dealing with similar problems, result_of can be a red herring.
I tried this with gcc 4.3, and it works with your example. However, I don't think this is a perfect approach for passing arbitrary callable objects. I confess I don't know all the issues off the top of my head, but I believe a better approach is to have call() accept only full-blown, first-class function objects as arguments. If users would like to pass built-in functions, they should first promote them to first-class function objects using boost::function/std::function.
Ouch.
Yeah, I guess that may sound a little harsh. ;-) But this constraint doesn't need to be onerous to actual end-users. With a simple level of indirection you could make the promotion transparent in most (if not all) cases. This can also be done while preserving the polymorphic behavior of both builtin function templates and function objects with templated operator(). Here's an example of a polymorphic unary call() that uses function<> to transparently promote builtin function templates to first-class. template <class F, class T> typename result_of<F(T)>::type call(F f, T t) { typedef function< typename result_of<F(T)>::type (T) > function_type; return call(function_type(f), t); } template<class T0, class T1> T0 call(function<T0(T1)> f, T1 t1) { return f(t1); } // A polymorphic builtin template<class T> int f(T) { return 0; } // A polymorphic functor struct g { typedef int result_type; template<class T> int operator()(T) { return 0; } }; // Various types struct t0 {}; struct t1 {}; int main() { // Call an unary builtin with various argument types call(f<t0>, t0()); call(f<t1>, t1()); // Call an unary functor with various argument types call(g(), t0()); call(g(), t1()); }
Of course, that opens another can of worms, if you'd like to preserve the polymorphic behavior of overloaded/templated built-ins.
Well you can't handle that case anyway; an overload set or a template doesn't have a type that can be passed on to result_of.
You can still preserve some polymorphic behavior by managing pointers to resolved overloads after the compiler has selected/generated a particular function from an overload set and/or template. I'm thinking of something along the lines of the multi-signature function that Marco uploaded to Vault last week. I'm not sure how helpful something like that would be for preserving builtin polymorphism, since the compiler is not selecting overloads based on the argument types at the call-site but based on whatever signatures you give it when the function is deferred. Unfortunately, you can defer builtin function calls, but you can't defer builtin function overload resolution. I doubt that there's a satisfying way to approximate the compiler's overload resolution in user code. But it would be cool if there were! Daniel Walker