4.1. Boolean Wrappers and Operationsbool is not just the simplest integral type, but also one of the most useful. Most of the type traits are bool-valued, and as mentioned earlier, play an important role in many metaprograms. The MPL type wrapper for bool values is defined this way: template< bool x > struct bool_ { static bool const value = x; // 1 typedef bool_<x> type; // 2 typedef bool value_type; // 3 operator bool() const { return x; } // 4 }; Let's walk through the commented lines above one at a time:
The library also supplies two convenient typedefs: typedef bool_<false> false_; typedef bool_<true> true_; 4.1.1. Type SelectionSo far, we've only made decisions at compile time by embedding them in ad hoc class template specializations: the terminating conditions of recursive algorithms (like the binary template we wrote in Chapter 1) say "if the argument is zero, calculate the result this way, otherwise, do it the other (default) way." We also specialized iter_swap_impl to select one of two implementations inside iter_swap: iter_swap_impl<use_swap>::do_it(*i1,*i2); Instead of hand-crafting a template specialized for each choice we make, we can take advantage of an MPL metafunction whose purpose is to make choices: mpl::if_<C,T,F>::type is T if C::value is TRue, and F otherwise. Returning to our iter_swap example, we can now use classes with mnemonic names in lieu of an iter_swap_impl template: #include <boost/mpl/if.hpp> struct fast_swap { template <class ForwardIterator1, class ForwardIterator2> static void do_it(ForwardIterator1 i1, ForwardIterator2 i2) { std::swap(*i1, *i2); } }; struct reliable_swap { template <class ForwardIterator1, class ForwardIterator2> static void do_it(ForwardIterator1 i1, ForwardIterator2 i2) { typename std::iterator_traits<ForwardIterator1>::value_type tmp = *i1; *i1 = *i2; *i2 = tmp; } }; The line of iter_swap that invoked iter_swap_impl's do_it member can be rewritten as: mpl::if_< mpl::bool_<use_swap> , fast_swap , reliable_swap >::type::do_it(i1,i2); That may not seem like much of an improvement: complexity has just been moved from the definition of iter_swap_impl into the body of iter_swap. It does clarify the code, though, by keeping the logic for choosing an implementation of iter_swap inside its definition. For another example, let's look at how we might optimize the passing of function parameters in generic code. In general, an argument type's copy-constructor might be expensive, so a generic function ought to accept parameters by reference. That said, it's usually wasteful to pass anything so trivial as a scalar type by reference: on some compilers, scalars are passed by value in registers, but when passed by reference they are forced onto the stack. What's called for is a metafunction, param_type<T>, that returns T when it is a scalar, and T const& otherwise. We might use it as follows:
template <class T>
class holder
{
public:
holder(typename param_type<T>::type x);
...
private:
T x;
};
The parameter to the constructor of holder<int> is of type int, while holder<std:: vector<int> >'s constructor takes a std::vector<int> const&. To implement param_type, we might use mpl::if_ as follows: #include <boost/mpl/if.hpp> #include <boost/type_traits/is_scalar.hpp> template <class T> struct param_type : mpl::if_< typename boost::is_scalar<T>::type , T , T const& > {}; Unfortunately, that implementation would prevent us from putting reference types in a holder: since it's illegal to form a reference to a reference, instantiating holder<int&> is an error. The Boost. Type Traits give us a workaround, since we can instantiate add_reference<T> on a reference typein that case it just returns its argument: #include <boost/mpl/if.hpp> #include <boost/type_traits/add_reference.hpp> template <class T> struct param_type : mpl::if_< typename boost::is_scalar<T>::type , T , typename boost::add_reference<T const>::type > {}; 4.1.2. Lazy Type SelectionThis approach isn't entirely satisfying, because it causes add_reference<T const> to be instantiated even if T is a scalar, wasting compilation time. Delaying a computation until it's absolutely needed is called lazy evaluation. Some functional programming languages, such as Haskell, do every computation lazily, with no special prompting. In C++, we have to do lazy evaluation explicitly. One way to delay instantiation of add_reference until it's needed is to have mpl::if_ select one of two nullary metafunctions, and then invoke the one selected: #include <boost/mpl/if.hpp> #include <boost/mpl/identity.hpp> #include <boost/type_traits/add_reference.hpp> template <class T> struct param_type : mpl::if_< // forwarding to selected transformation typename boost::is_scalar<T>::type , mpl::identity<T> , boost::add_reference<T const> >::type {}; Note our use of mpl::identity, a metafunction that simply returns its argument. Now param_type<T> returns the result of invoking either mpl::identity<T> or boost:: add_reference<T const>, depending on whether T is a scalar. This idiom is so common in metaprograms that MPL supplies a metafunction called eval_if, defined this way: template <class C, class TrueMetafunc, class FalseMetafunc> struct eval_if : mpl::if_<C,TrueMetafunc,FalseMetafunc>::type {}; Whereas if_ returns one of two arguments based on a condition, eval_if invokes one of two nullary metafunction arguments based on a condition and returns the result. We can now simplify our definition of param_type slightly by forwarding directly to eval_if: #include <boost/mpl/eval_if.hpp> #include <boost/mpl/identity.hpp> #include <boost/type_traits/add_reference.hpp> template <class T> struct param_type : mpl::eval_if< typename boost::is_scalar<T>::type , mpl::identity<T> , boost::add_reference<T const> > // no ::type here {}; By taking advantage of the fact that Boost's integral metafunctions all supply a nested ::value, we can make yet another simplification to param_type:
template <class T>
struct param_type
: mpl::eval_if<
boost::is_scalar<T>
, mpl::identity<T>
, boost::add_reference<T const>
>
{};
Specializations of Boost metafunctions that, like is_scalar, return integral constant wrappers, happen to be publicly derived from those very same wrappers. As a result, the metafunction specializations are not just valid integral constant wrappers in their own right, but they inherit all the useful properties outlined above for wrappers such as bool_: if (boost::is_scalar<X>()) // invokes inherited operator bool() { // code here runs iff X is a scalar type } 4.1.3. Logical OperatorsSuppose for a moment that we didn't have such a smart add_reference at our disposal. If add_reference were just defined as shown below, we wouldn't be able to rely on it to avoid forming references to references: template <class T> struct add_reference { typedef T& type; }; In that case, we'd want to do something like this with param_type to avoid passing references to add_reference: template <class T> struct param_type : mpl::eval_if< mpl::bool_< boost::is_scalar<T>::value || boost::is_reference<T>::value > , mpl::identity<T> , add_reference<T const> > {}; Pretty ugly, right? Most of the syntactic cleanliness of our previous version has been lost. If we wanted to build a lambda expression for param_type on-the-fly instead of writing a new metafunction, we'd have even worse problems: typedef mpl::vector<int, long, std::string> argument_types; // build a list of parameter types for the argument types typedef mpl::transform< argument_types , mpl::if_< mpl::bool_< boost::is_scalar<_1>::value || boost::is_reference<_1>::value > , mpl::identity<_1> , add_reference<boost::add_const<_1> > > >::type param_types; This one isn't just ugly, it actually fails to work properly. Because touching a template's nested ::value forces instantiation, the logical expression boost::is_scalar<_1>::value || is_reference<_1>::value is evaluated immediately. Since _1 is neither a scalar nor a reference, the result is false, and our lambda expression is equivalent to add_reference<boost:: add_const<_1> >. We can solve both of these problems by taking advantage of MPL's logical operator metafunctions. Using mpl::or_, we can recapture the syntactic cleanliness of our original param_type:
#include <boost/mpl/or.hpp>
template <class T>
struct param_type
: mpl::eval_if<
mpl::or_<boost::is_scalar<T>, boost::is_reference<T> >
, mpl::identity<T>
, add_reference<T const>
>
{};
Because mpl::or_<x,y> is derived from its result ::type (a specialization of bool_<n>), and is thus itself a valid MPL Boolean constant wrapper, we have been able to completely eliminate the explicit use of bool_ and access to a nested ::type. Despite the fact that we're not using operator notation, the code is actually more readable than before. Similar benefits accrue when we apply the same change to our lambda expression, and it works properly, to boot:
typedef mpl::transform<
argument_types
, mpl::if_<
mpl::or_<boost::is_scalar<_1>, boost::is_reference<_1> >
, mpl::identity<_1>
, add_reference<boost::add_const<_1> >
>
>::type param_types;
What if we wanted to change param_type to pass all stateless class types, in addition to scalars, by value? We could simply nest another invocation of or_: # include <boost/type_traits/is_stateless.hpp> template <class T> struct param_type : mpl::eval_if< mpl::or_< boost::is_stateless<T> , mpl::or_< boost::is_scalar<T> , boost::is_reference<T> > > , mpl::identity<T> , add_reference<T const> > {}; While that works, we can do better. mpl::or_ accepts anywhere from two to five arguments, so we can just write: # include <boost/type_traits/is_stateless.hpp> template <class T> struct param_type : mpl::eval_if< mpl::or_< boost::is_scalar<T> , boost::is_stateless<T> , boost::is_reference<T> > , mpl::identity<T> , add_reference<T const> > {}; In fact, most of the MPL metafunctions that operate on integral arguments (e.g., mpl:: plus<...>) have the same property. The library contains a similar and_ metafunction, and a unary not_ metafunction for inverting Boolean conditions.[1] It's worth noting that, just like the built-in operators && and ||, mpl::and_ and mpl::or_ exhibit "short circuit" behavior. For example, in the example above, if T is a scalar, boost::is_stateless<T> and is_reference<T> will never be instantiated.
![]() |