|
Boost : |
From: Richard Smith (richard_at_[hidden])
Date: 2006-11-09 11:49:47
I've spent some time this afternoon reviewing the contents
of the function type library as its functionality is
something I feel is currently missing from Boost and would
like to see added. Although most of my comments are
negative, I like this library, and would like to see it in
Boost. I would prefer to see some modifications made first,
but even in the absense of these, I'd still like to see it
added.
There were several different links posted; I'm assuming that
<http://tinyurl.com/qaf5f> is the correct one.
Throughout these comments, I am assuming the namespace alias
namespace bft = boost::function_types;
1. Namespace
The public interface to this library is in the
boost::function_types namespace. As a propsective user of
the library, it feels very much like an extension to the
type_traits library, and that leaves me wondering why the
library isn't in the main boost namespace with the other
type_traits functionality.
2. Use of Tags
After a second glance, it's clear that one reason for
having separate namespaces is to avoid name collisions --
we already have a boost::is_function, for example. And
this leaves me wondering how boost::is_function differs
from bft::is_function as the names fail to convey the
distinction. So far as I can see, the additional Tag
template parameter is the only difference, and only three
of the possible property tags are meaningful: variadic,
non_variadic and default_cc.
The majority of the time I can't imagine myself making
much use of any of these tags. Are they really worth
while? The 'Rationale' section of the documentation
addresses this and considers some alternatives:
| The first one is just using more templates so every
| property has to be asked for explicitly. This approach
| results in more complicated client code if more than one
| propery has to be checked and in a exponentially larger
| library interface.
|
| The second alternative is having the client pass in bit
| patterns via non-type template parameters. The logic has
| to be performed by the client and there are much more
| error conditions. Further, class templates with non-type
| template parameters do not work within MPL lambda
| expressions and can cause problems with older compilers.
What about the third possibility: leave it up to the
client to sythesise these more complicated examples. It
will involve adding a few more templates: notably
is_variadic and is_default_cc. Then, instead of writing
bft::is_function< T, bft::variadic >::value
you would write
boost:is_function<T>::value && boost::is_variadic<T>::value
... which is not significantly more verbose. I think this
would be my prefered interface as it would avoid the
duplication of functionality between the type_traits
library (and the potential for confusion that this would
cause).
3. Extensibility
The internal bitmask implementation underlying the
property tags does not appear to allow extension by a
user. For example, there is a default_cc tag; suppose I
want to add support for querying the some other calling
convention. Can I write a fastcall_cc tag? Scanning
through the implementation it would appear not --
certainly there's no documentation on how to do so.
If a tag-like syntax is considered desireable, wouldn't
something like
template < typename T,
template <typename> class UnaryTypeTrait >
struct is_function
: integral_constant< bool,
is_function<T>::value && UnaryTypeTrait<T>::value
>
{};
be more extensible? Or does this have to work with
compilers that can't handle template template parameters?
4. Naming
It seems unlikely that the default_cc tag will be
particularly heavily used. Given this, why not give it a
more meaningful name: default_calling_convention perhaps?
5. Extern "C" linkage
What should this print?
extern "C" { typedef int fn(); }
int main() {
std::cout << bfs::is_function<fn, bfs::default_cc>::value
<< std::endl;
}
As function types differing only by language linkage are
distinct types (7.4/1) (even though many compilers don't
respect this), they can, in principle, have differing
calling conventions, in which case the extern "C" calling
convention is not default.
This may be a non-issue as I don't know whether any of the
compilers that Boost targets actually take advantage of
this, and even if they do, I don't even know whether it is
possible to detect "C" language linkage with template
metaprogramming (mostly because you can't put extern "C"
within a template or atemplate within an extern "C"
block).
6. What is a 'callable builtin'?
So far as I can see, the C++ Standard does not define
'callable', so I asume that the pertinent definition
is the one in TR1, 3.1/6:
| A /callable type/ is a pointer to function, a pointer to
| member function, a pointer to member data, or a class
| type whose objects can appear immediately to the left of
| a function call operator.
By this definition, function types are not callable, nor
are references to functions, bizarre as this may seem.
I can't find any definition of 'builtin' in either the
Standard or TR1, though it is occasionally used in the
Standard to refer to integral or arithmetic types --
clearly this isn't what's meant here, though!
It would appear that in this context 'callable builtin' is
supposed to include function types, pointers or references
to function types and pointers to member functions. It
does not include classes with an overloaded operator()
(unsurpisingly -- it would be a very strange definition of
'builtin' that included them); nor does it include
pointers to member data, even though they are 'callable'
per TR1 and are seemingly as 'builtin' as pointers to
member functions.
Though I'm not wholly convinced with TR1's definition of
'callable', this is as close as it gets to a standard
definition, and it would seem wise to make pointers to
member data return true.
I also think 'builtin' needs documenting even if only
superficially; to me 'builtin' is a synonym to
'fundamental', though there are no callable fundamental
types.
7. What is a 'callable scalar'?
The term 'scalar' is defined in 3.9/10:
| Arithmentic types, enumeration types, pointer types, and
| pointer to member types, and cv-qualified versions of
| these types are collectively called scalar types.
Note that references, whether to functions or otherwise,
are *not* scalars. The is_callable_scalar metafunction
returns true for references to functions. This is wrong.
Also, why is is_callable_scalar useful? I'm not sure I
can think of a use case; and if I could, I imagine it
would be rare enough that it wouldn't be much of an
inconvenience to sythesise it from is_function and
is_callable_builtin.
8. 'Tag Types' documentation
In several places, the 'Tag Types' documentation
incorrectly gives types two leading underscores.
9. Extending result_type
Perhaps this is beyond the intended scope of the library,
but I think it would be very useful to extend the domain
of this metafunction to include class types with a
result_type member type. The rationale of this is that in
many ways bft::result_type is a simplified version of
boost::result_of -- simplified so that the types of the
invokation arguments need not be known.
In other words, if both of these compile
result_of< Fn( T1, T2, ... ) >::type
result_type< Fn >::type
compile, they will refer to the same type. (This is
also true of the current implementation without my
suggested extention.)
10. Syntactic sugar around parameter_types
Whilst being able to leverage the full power of the MPL
with parameter_types is definitely desireable, it can also
faze those less familiar with metaprogramming.
I would like to suggest a helper metafunction that
extracts a single parameter type:
template < typename F, std::size_t N,
class ClassTransform = add_reference<_> >
struct function_parameter {
typedef typedef mpl::at<
function_parameters<F, ClassTransform>, N
>::type type;
};
(I'd suggest base-zero numbering as given here, though
this might cause confusion in conjunction with the
first_argument_type, second_argument_type typedefs in
std::binary_function.)
Oh, and I'd also like to suggest a quick comment in the
documentation saying what the '_' is that is given as a
template argument to add_reference -- a quick
using mpl::placeholders::_;
in the synopsis would probably be sufficient.
11. Member function arities
The documentation to function_arity states that the 'this'
pointer is included -- which is what most people would
expect. It's also what the implementation does. However
in the 'Use Cases' page of documentation, a R (C::*)()
function is listed in the first code fragment under the
comment '0 parameters'. Although not explicitly
contradictory (parameters and arity don't *necessarily*
mean the same thing), I think this is potentially
confusing.
12. Use cases documentation
To be honest, I find this page of the documentation
dreadful, which is shame as the rest is quite reasonable.
It discusses various scenarios that could benefit from the
application of this library, but utterly fails to suggest
*how* they would benefit from the library.
On the whole page, only one of the library's components is
actually mentioned -- the function_pointer metafunction
for synthesising function types. But the code fragment is
far too incomplete to be of any real use. It's also one
of the more complicated examples as it requires using the
MPL. By all means include such a example, but not as the
only one!
13. Why is bft::components useful?
For that matter, what *exactly* does it do? First the
documentation says
| components<T,ClassTransform>
| MPL - Front / Back Extensible Random Access Sequence
| of all component types and property tag
then it says
| Extracts all properties of a callable builtin type, that
| is the result type, followed by the parameter types
| (including the type of this for member function
| pointers).
The former is ambiguous -- it doesn't say what it means by
'component types'; and the latter contradicts it by not
mentioning the property tag.
It also seems not to mention what it actually means by
*the* property tag. A function can be variadic or
non_variadic; it be default_cc; it may have cv-qualifiers,
which in the case of a const volatile function needs to
tags to express it. Are these concatenated using bft::tag
or appended one-by-one to the end of the MPL sequence?
But more importantly, why do we need this metafunction?
It does nothing that separate invokations of result_type,
parameter_types and the various is_* metafunctions can't
achieve. And I would *rather* see separate invokations of
the these -- it would make for much more readble code.
Oh, and the name isn't very descriptive.
14. Type sythesis
... And now I see what bft::components is supposed to
achieve.
But I still don't find the idea of merging the result
type, parameter types and arbitrary other information
together into a single type list at all intuitive.
What's wrong with:
template < typename Result, class ParameterSeq,
class PropertiesTag = null_tag >
struct function_type;
Much more readable, in my opinion.
Having said that, I think these metafunctions are an
important piece of functionality. It's merely their
interface I'm concerned about.
I hope these points have been useful -- and sorry for such a
long post if they haven't!
Cheers
Richard Smith
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk