Boost logo

Boost :

From: Tobias Schwinger (tschwinger_at_[hidden])
Date: 2005-06-16 20:47:59


Hello John,

John Maddock wrote:
> This is a kind of first look, review still in progress set of comments:
>

thank you for your first look, then.

>
>>What is your evaluation of the design?
>
>
> Overall it looks mostly fine.
>
> function_type_class: if the member function is cv-qualified is the result of
> this metafunction cv-qualified also? If not is there a way to tell if a
> member function is cv-qualified?

Yes. Currently it's:

   is_function_type<const_member_function_pointer,T>::value

However, I will change this. So it will be:

   is_const< function_type_class<T>::type >::value

which is what you propose and way more practical. I use a wrapper to model this
in many places and I really want to get rid of it.

>
> Reading the docs, I don't understand what function_type_signature is for,
> especially when it's members are not recomended for general use. Should
> this be an implementation detail?
>

No, but the docs need to be fixed, here.

'function_type_signature' a model of MPL Random Access sequence (even an
"extensible" one if the synthesis part has been enabled by #including
'function_type.hpp').

The 'kind' member is the "missing piece of information" to fully describe the
encapsulated type (in combination with the subtypes stored in the sequence).

The 'representee' member is neccessary to get the type which it (e.g. a modified
sequence) describes.

Further (what follows is a preliminary try to improve this recommendation):

      'function_type_signature' can be used to achieve similar functionality as
      provided by the decomposition components.

      This application, however, is not recommended (*), because there is
      no implied assertion that its template argument really is a function type
      and because its type members form a less expressive interface for
      decomposistion.

      (*) It can be an opportunity for optimization in heavily repetitive
      situations to reduce the number of template instantiations required or
      to avoid to depend on the synthesis part of the library if an extensible
      sequence is needed which does not have to be reassembled to a function
      type.

> function_type: OK I understand what it does, but I'm having a hard time
> figuring out what it would be for. It's one thing to be able to create a
> specific function type, but then you have to be able to do something with it
> ;-)
>

Like taking the address of an overloaded function or function template
instantiation ??

Ref:
   13.4 Address of overloaded function
   14.8.2.2 Deducing template arguments taking the address of a function template
   14.8.2.4 Deducing template arguments from a type

We can even declare functions this way, as long as we don't define them
[8.3.5-7] - although I can't really imagine a situation where this one makes
a lot of sense, right now ;-).

Or another one:

Let's say we have some function type and want to create a template argument for
Boost.Function with an optimal forwarding signature (i.e. use
call_traits<T>::param_type to compute the parameter types). For member function
pointers we will parametrize the transformation applied to the class type (and
set the default to add a reference):

     // MPL friendly wrapper for call_traits<T>::param_type
     template<typename T> struct param_type
     {
       typedef typename call_traits<T>::param_type type;
     };

     // Metafunction to compute an optimized forwarding signature
     template
     < typename FunctionType
     , typename ClassTypeTransform = add_reference<_1>
>
     struct forward_signature_function_type
       : function_type
         < plain_function
         , typename mpl::copy
           < function_type_signature<FunctionType>
           , mpl::inserter
             < mpl::vector<>
             , mpl::push_back
               < _1
               , mpl::if_
                 < mpl::greater
                   < mpl::size<_1>
                   , typename mpl::if_
                     < is_function_type<member_function_pointer,FunctionType>
                     , mpl::size_t<1>
                     , mpl::size_t<0>
>::type
>
                 , param_type<_2>
                 , mpl::if_
                   < mpl::equal_to< mpl::size<_1>, mpl::size_t<1> >
                   , mpl::apply1
                     < typename mpl::lambda<ClassTypeTransform>::type, _2 >
                   , _2
> > > > >::type
>
     { };

OK, OK - I admit, this implementation is ugly.

>
>>What is your evaluation of the implementation?
>
>
> Looks OK as far as I've been able to judge.
>
> Some commenbts on the tests: I see lots of tests for is_function_type and
> very little for the composition and decompostion functions. I'd like to see
> the same sort of thoughoughness that's gone into arity_and_type.cpp applied
> to the other templates (probably one test for each).
>

As I plan to extend portability, I won't get around writing more proper regressions.

Type synthesis needs better testing most badly.

Currently, there is not that much to test in terms of decomposition. Correct
arity and tag means 'signature_impl' (the heart of all classification and
decomposition) works. Of course I could test every type, but it's really just
template specialization and there is little chance for typos in generated code.

The "tag logic" is based on binary arithmetic and the part of the encoding that
is edited by hand is quite small. Errors in there are caught pretty well by the
"example/test hybrids". But they are by no means perfect, yet.

>
>>What is your evaluation of the documentation?
>
>
> OK as a reference document (modulo Dave Abrahams comments, which should be
> addressed).
>
> The big issue I have is with the motivation section: the problem I have is
> that the usefullness of the library isn't immediately obvious to me. Some
> of the worked examples might address this, but the interpreter example to
> pick just one is quite complicated, so some kind of tutorial is required
> here.
>
> The links to the examples, look more like links to tests to me, it would be
> much more comprehensible if there were code snippets to illustrate the
> points you're trying to make directly in the documentation.
>

I agree. In fact, I'm already collecting more illustrative code snippets.

>
>>What is your evaluation of the potential usefulness of the library?
>
>
> This is the real problem, I can see how a function's return type and arity
> may be useful, but I'm having a hard time figuring out what access to the
> type of specific function arguments is good for,

OK. Let's start with the conservative side of the story:

Of course we can always spell things out like this:

     template<typename R>
     void do_something_with_function(R (*)());
     template<typename R,typename A1>
     void do_something_with_function(R (*)(A1));
     template<typename R,typename A1,typename A2>
     void do_something_with_function(R (*)(A1,A2));
     //...

And in this case we know all paramter types, of course.

When doing complex things we end up writing several of these cascades (as we
usually don't get all work done in one function), writing code generators to
help us with it, add configuration options for __stdcall, __fastcall, etc. or
forget them. The neighbouring library does the same thing.

If we can completely decompose function types we won't need to do this. We can
just use a simple template function instead:

     template<typename FuncPtr>
     void do_something_with_function(FuncPtr f)
     {
        // maybe asserts f really is a function pointer
     }

Well, we won't get rid of repetitive parts entirely, because of the invocation.

However, we can reduce them. This code reduction is especially significant when
we are supporting cv-qualified member function pointers or several different
calling conventions. We don't have to deal with this stuff anymore at all
because the invocation doesn't care. The decomposition facility handles it for us.

But this it's not only about reducing thousand lines of code, supporting exotic
things like variadic functions or non-standard calling conventions and a common
point of configuration for it - it's about software design: making design
choices is much easier knowing there is a choice, so the existence of library is
going to influence future design decisions.

Nice words to shift over to some more progressive things like (what I call)
"intelligent" callback facilities: The basic idea is to generate functionality
that feeds a function with arguments based on its signature.

The interpreter example tries to illustrate this by providing a limited command
line interpreter that allows to call previously registered C++ functions and
simple functors (lexical_cast is applied to input from a token_iterator to feed
the function).
Taking this idea a bit further, making it e.g. a spirit parser with parametrized
(per-parameter-type) argument parsing, also handling the result, would make it a
matter of minutes to layer a functional scripting languages over existing C++ code.

An event processing framwork could use the same technique to hand event handler
functions the data they are interested in, for example.

> likewise what the mpl-list based composition/decomposition functions are for.

E.g:

o Setting up a table of function signatures and taking the address of a function
overloaded this way by index.

o Concept checking (does my function take "key_t, value_t" n-times ?).

o Doing transformations (like the weird code above does - I guess there are
better examples out there).

o Writing a traits class to probe the number of default arguments of a function
with a particular name (operator() would be a good one, I guess).

> Better motivating examples may help, however there is a seems to be
> substantial overlap between the functionality offered here and boost::bind,
> boost::function etc. Is this library intended to simplify those libraries?

Not exclusively.

There is potential to improve existing Boost code, already. But this library
must become more portable to be applied universally.

Not much to do for Function, since it doesn't really do a lot with the types (it
either uses MemFn or a cast to void*). Once the portability matches, three lines
(or so) can be changed to use this library for classification (it'll work for
__stdcall and friends then, given FunctionTypes is configured to support them).

Lambda and Phoenix will benefit from using this library - and the ResultOf
utiltiy too, of course.

I'm not sure on Bind as a whole, yet. boost::mem_fn (which seems to be a part of
Bind) is a candidate, though.

> If so what do those libraries authors think?
>

I'ld really love to know!

In fact, AFAIK you are one of them (maintainer, at least) as there is quite some
overlap with TypeTraits.
However, FunctionTypes is not portable enough for this, yet. And when it is, it
will require a lot of precision not to end up with circular dependencies.
Currently only remove_cv is used directly so there is a chance it's doable...

Regards,

Tobias


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