Boost logo

Boost :

From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2008-05-06 15:46:44


On Thu, May 1, 2008 at 2:56 PM, David Abrahams
<dave_at_[hidden]> 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_at_[hidden]> 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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk