123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- [/
- / 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]
|