175 lines
6.2 KiB
Plaintext
175 lines
6.2 KiB
Plaintext
|
[/
|
||
|
/ Copyright (c) 2008 Eric Niebler
|
||
|
/
|
||
|
/ Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||
|
/ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||
|
/]
|
||
|
|
||
|
[section:implementation Appendix D: Implementation Notes]
|
||
|
|
||
|
[section:sfinae Quick-n-Dirty Type Categorization]
|
||
|
|
||
|
Much has already been written about dispatching on type traits using
|
||
|
SFINAE (Substitution Failure Is Not An Error) techniques in C++. There
|
||
|
is a Boost library, Boost.Enable_if, to make the technique idiomatic.
|
||
|
Proto dispatches on type traits extensively, but it doesn't use
|
||
|
`enable_if<>` very often. Rather, it dispatches based on the presence
|
||
|
or absence of nested types, often typedefs for void.
|
||
|
|
||
|
Consider the implementation of `is_expr<>`. It could have been written
|
||
|
as something like this:
|
||
|
|
||
|
template<typename T>
|
||
|
struct is_expr
|
||
|
: is_base_and_derived<proto::some_expr_base, T>
|
||
|
{};
|
||
|
|
||
|
Rather, it is implemented as this:
|
||
|
|
||
|
template<typename T, typename Void = void>
|
||
|
struct is_expr
|
||
|
: mpl::false_
|
||
|
{};
|
||
|
|
||
|
template<typename T>
|
||
|
struct is_expr<T, typename T::proto_is_expr_>
|
||
|
: mpl::true_
|
||
|
{};
|
||
|
|
||
|
This relies on the fact that the specialization will be preferred
|
||
|
if `T` has a nested `proto_is_expr_` that is a typedef for `void`.
|
||
|
All Proto expression types have such a nested typedef.
|
||
|
|
||
|
Why does Proto do it this way? The reason is because, after running
|
||
|
extensive benchmarks while trying to improve compile times, I have
|
||
|
found that this approach compiles faster. It requires exactly one
|
||
|
template instantiation. The other approach requires at least 2:
|
||
|
`is_expr<>` and `is_base_and_derived<>`, plus whatever templates
|
||
|
`is_base_and_derived<>` may instantiate.
|
||
|
|
||
|
[endsect]
|
||
|
|
||
|
[section:function_arity Detecting the Arity of Function Objects]
|
||
|
|
||
|
In several places, Proto needs to know whether or not a function
|
||
|
object `Fun` can be called with certain parameters and take a
|
||
|
fallback action if not. This happens in _callable_context_ and
|
||
|
in the _call_ transform. How does Proto know? It involves some
|
||
|
tricky metaprogramming. Here's how.
|
||
|
|
||
|
Another way of framing the question is by trying to implement
|
||
|
the following `can_be_called<>` Boolean metafunction, which
|
||
|
checks to see if a function object `Fun` can be called with
|
||
|
parameters of type `A` and `B`:
|
||
|
|
||
|
template<typename Fun, typename A, typename B>
|
||
|
struct can_be_called;
|
||
|
|
||
|
First, we define the following `dont_care` struct, which has an
|
||
|
implicit conversion from anything. And not just any implicit
|
||
|
conversion; it has a ellipsis conversion, which is the worst possible
|
||
|
conversion for the purposes of overload resolution:
|
||
|
|
||
|
struct dont_care
|
||
|
{
|
||
|
dont_care(...);
|
||
|
};
|
||
|
|
||
|
We also need some private type known only to us with an overloaded
|
||
|
comma operator (!), and some functions that detect the presence of
|
||
|
this type and return types with different sizes, as follows:
|
||
|
|
||
|
struct private_type
|
||
|
{
|
||
|
private_type const &operator,(int) const;
|
||
|
};
|
||
|
|
||
|
typedef char yes_type; // sizeof(yes_type) == 1
|
||
|
typedef char (&no_type)[2]; // sizeof(no_type) == 2
|
||
|
|
||
|
template<typename T>
|
||
|
no_type is_private_type(T const &);
|
||
|
|
||
|
yes_type is_private_type(private_type const &);
|
||
|
|
||
|
Next, we implement a binary function object wrapper with a very
|
||
|
strange conversion operator, whose meaning will become clear later.
|
||
|
|
||
|
template<typename Fun>
|
||
|
struct funwrap2 : Fun
|
||
|
{
|
||
|
funwrap2();
|
||
|
typedef private_type const &(*pointer_to_function)(dont_care, dont_care);
|
||
|
operator pointer_to_function() const;
|
||
|
};
|
||
|
|
||
|
With all of these bits and pieces, we can implement `can_be_called<>` as
|
||
|
follows:
|
||
|
|
||
|
template<typename Fun, typename A, typename B>
|
||
|
struct can_be_called
|
||
|
{
|
||
|
static funwrap2<Fun> &fun;
|
||
|
static A &a;
|
||
|
static B &b;
|
||
|
|
||
|
static bool const value = (
|
||
|
sizeof(no_type) == sizeof(is_private_type( (fun(a,b), 0) ))
|
||
|
);
|
||
|
|
||
|
typedef mpl::bool_<value> type;
|
||
|
};
|
||
|
|
||
|
The idea is to make it so that `fun(a,b)` will always compile by adding
|
||
|
our own binary function overload, but doing it in such a way that we can
|
||
|
detect whether our overload was selected or not. And we rig it so that
|
||
|
our overload is selected if there is really no better option. What follows
|
||
|
is a description of how `can_be_called<>` works.
|
||
|
|
||
|
We wrap `Fun` in a type that has an implicit conversion to a pointer to
|
||
|
a binary function. An object `fun` of class type can be invoked as
|
||
|
`fun(a, b)` if it has such a conversion operator, but since it involves
|
||
|
a user-defined conversion operator, it is less preferred than an
|
||
|
overloaded `operator()`, which requires no such conversion.
|
||
|
|
||
|
The function pointer can accept any two arguments by virtue
|
||
|
of the `dont_care` type. The conversion sequence for each argument is
|
||
|
guaranteed to be the worst possible conversion sequence: an implicit
|
||
|
conversion through an ellipsis, and a user-defined conversion to
|
||
|
`dont_care`. In total, it means that `funwrap2<Fun>()(a, b)` will
|
||
|
always compile, but it will select our overload only if there really is
|
||
|
no better option.
|
||
|
|
||
|
If there is a better option --- for example if `Fun` has an overloaded
|
||
|
function call operator such as `void operator()(A a, B b)` --- then
|
||
|
`fun(a, b)` will resolve to that one instead. The question now is how
|
||
|
to detect which function got picked by overload resolution.
|
||
|
|
||
|
Notice how `fun(a, b)` appears in `can_be_called<>`: `(fun(a, b), 0)`.
|
||
|
Why do we use the comma operator there? The reason is because we are
|
||
|
using this expression as the argument to a function. If the return type
|
||
|
of `fun(a, b)` is `void`, it cannot legally be used as an argument to
|
||
|
a function. The comma operator sidesteps the issue.
|
||
|
|
||
|
This should also make plain the purpose of the overloaded comma operator
|
||
|
in `private_type`. The return type of the pointer to function is
|
||
|
`private_type`. If overload resolution selects our overload, then the
|
||
|
type of `(fun(a, b), 0)` is `private_type`. Otherwise, it is `int`.
|
||
|
That fact is used to dispatch to either overload of `is_private_type()`,
|
||
|
which encodes its answer in the size of its return type.
|
||
|
|
||
|
That's how it works with binary functions. Now repeat the above process
|
||
|
for functions up to some predefined function arity, and you're done.
|
||
|
|
||
|
[endsect]
|
||
|
|
||
|
[/
|
||
|
[section:ppmp_vs_tmp Avoiding Template Instiations With The Preprocessor]
|
||
|
|
||
|
TODO
|
||
|
|
||
|
[endsect]
|
||
|
]
|
||
|
|
||
|
[endsect]
|