2.5 Parameter-Enabled Class Templates, A Second Example Parameters on class templates have some unique features and limitations, so here is a second, very different example based on a simplified version of the integer_t class from the (as-yet unofficial) Boost.XInt library. Our simplified integer_t class template will take four different parameters, all optional: * A size_t number, for fixed-length integers (zero for variable-length ones); * A Boolean value for "secure mode"; * An enumerated value specifying how to handle negative numbers; * An allocator class. Notice that only one of those parameters is an actual class. Our goal is to use Boost.Parameter to create a class interface where the items can be specified in any order, and only those that are different from their defaults need to be specified, all with a minimum of syntactic noise. 2.5.1 Using Value Parameters Boost.Parameter only supports types, not values, for parameters to class templates. If you need to use values instead, such as the size_t value for integer_t's bit-length, you have to use wrapper classes. 2.5.1.1 The Conventional Method Here is an obvious way to do this: template struct fixedlength { static const std::size_t bits; }; template const std::size_t fixedlength::bits = Bits; Unfortunately, there are some cases where this doesn't work. 2.5.1.2 A More Universally Compatible Method While the conventional method works for most uses, it fails under Microsoft Visual C++ (tested under MSVC8/2005) when trying to use the "bits" as a template parameter for another class. For example, if you try to compile this under MSVC, you'll see the problem: #include template struct fixedlength { static const std::size_t bits; }; template const std::size_t fixedlength::bits = Bits; template struct uses_fixedlength { // ... }; int main() { using std::cout; using std::endl; typedef fixedlength<128> L; typedef uses_fixedlength M; // ... return 0; } What you get is: error C2975: 'Bits' : invalid template argument for 'uses_fixedlength', expected compile-time constant expression To solve this, we can use the wrappers provided by Boost.MPL: #include #include template struct fixedlength: public boost::mpl::size_t { }; template struct uses_fixedlength { // ... }; int main() { using std::cout; using std::endl; typedef fixedlength<128> L; typedef uses_fixedlength M; // ... return 0; } (Although the code provided here could get by with the conventional method, we'll use this one for the remainder of this example.) With that problem out of the way, we can continue. 2.5.2 Set up Boost.Parameter The setup is almost identical to the previous example. First, toss in some additional headers at the top: #include #include Here are the template keywords: BOOST_PARAMETER_TEMPLATE_KEYWORD(fixedlength) BOOST_PARAMETER_TEMPLATE_KEYWORD(secure) BOOST_PARAMETER_TEMPLATE_KEYWORD(allocator) BOOST_PARAMETER_TEMPLATE_KEYWORD(negatives) And here's the first cut at the signature: typedef boost::parameter::parameters< tag::fixedlength, tag::secure, tag::negatives, tag::allocator > integer_signature; Finally, the skeleton code for the class template, with the argument pack: template < class A0 = boost::parameter::void_, class A1 = boost::parameter::void_, class A2 = boost::parameter::void_, class A3 = boost::parameter::void_ > class integer_t { public: typedef typename integer_signature::bind::type args; typedef typename boost::parameter::binding::type fixedlength; typedef typename boost::parameter::binding::type negatives; typedef typename boost::parameter::binding::type secure; typedef typename boost::parameter::binding::type allocator; static void report() { using std::cout; using std::endl; cout << "Length: " << fixedlength::value << endl; cout << "Negatives option: " << negatives::value << endl; cout << "Secure: " << (secure::value ? "true" : "false") << endl; cout << "Allocator type size: " << sizeof(typename allocator::value_type) << endl; } }; At this point, we have enough code to exercise it, with the following: enum negatives_options { default_option, option_1 = 1, option_2 = 2, option_3 = 3, }; template struct fixedlength_t: public boost::mpl::size_t { }; template struct negatives_t: public boost::mpl::integral_c { }; template struct secure_t: public boost::mpl::bool_ { }; int main() { integer_t< secure >, allocator >, negatives >, fixedlength > >::report(); return 0; } We've already realized one of our goals: the parameters can be specified in any order. But at present, they all must be specified. We'll tackle that next. 2.5.3 Optional Parameters Making parameters optional requires only two steps. First, modify the signature: using boost::parameter::optional; typedef boost::parameter::parameters< optional, optional, optional, optional > integer_signature; And second, provide defaults for the optional parameters. This is done in the bindings, by adding a third parameter: // ... class integer_t { public: typedef typename integer_signature::bind::type args; typedef typename boost::parameter::binding >::type fixedlength; typedef typename boost::parameter::binding >::type negatives; typedef typename boost::parameter::binding >::type secure; typedef typename boost::parameter::binding >::type allocator; // ... You'll also need to move negatives_options, fixedlength_t, negatives_t, and secure_t to a point above the declaration of the integer_t class. Put them just under the includes. Now you can specify only those items that differ from their defaults, achieving the second of our goals. 2.5.4 Eliminating Tags By Deducing Them From Types If you take a look at the main function, you might notice that every value option has to be specified two ways: with the tag, and again with the wrapper. Since we must have wrappers for values, and we've made separate wrappers for each one, it would be nice if we could somehow eliminate the extra verbosity. Boost.Parameter provides a deduced parameter interface for this very purpose. We'll start small on this one, and just try to deduce the fixed-length item by modifying the signature like so: using boost::parameter::optional; using boost::parameter::deduced; typedef boost::parameter::parameters< optional >, // Error, not enough information! optional, optional, optional > integer_signature; But now, if you try to use the fixedlength_t wrapper without the tag... int main() { integer_t< allocator >, negatives >, fixedlength_t<128> >::report(); return 0; } ...it won't compile. The problem is that we haven't told Boost.Parameter *how* to deduce the type yet. 2.5.4.1 Deducing From Template Types Here we run into a problem. We need to tell Boost.Parameter to deduce the fixedlength tag from the fixedlength_t type, which we can do using a combination of the Boost.MPL and Boost.TypeTraits libraries. But the compiler doesn't recognize a single fixedlength_t type; if we try to do it in the obvious way... // Error, fixedlength_t isn't a type without specifying its template parameter typedef boost::parameter::parameters< optional, boost::is_same >, optional, optional, optional > integer_signature; ...the compiler complains, because fixedlength_t on its own doesn't describe a complete type. You have to specify its Bits parameter for that. And of course, if we specified a parameter there, it would only match items with that specific parameter, defeating the purpose of the exercise. As you might have already figured out, the solution is to use a non-template base class for fixedlength_t, and is_base_of from Boost.TypeTraits: struct fixedlength_base_t { }; template struct fixedlength_t: public boost::mpl::size_t, public fixedlength_base_t { }; using boost::parameter::optional; using boost::parameter::deduced; using boost::mpl::_; using boost::is_base_of; typedef boost::parameter::parameters< optional, is_base_of >, optional, optional, optional > integer_signature; Viola -- problem solved. Now you can leave out the fixedlength tag, and just specify the fixedlength_t type... int main() { integer_t< allocator >, negatives >, fixedlength_t<128> >::report(); return 0; } ...and the compiler will accept and understand it, a fact you can confirm by running the program and seeing that it prints out 128 on the length line. So we'll finish this by doing the same for the other three types. 2.5.4.2 Deducing The allocator Type That works fine until we get to the allocator type. We don't control the specifications for the allocator class, so we can't insist that it be derived from a specific base class. There's nothing in Boost.TypeTraits that can test for the existence of members of a class, or typedefs within them, which is the only way that the compiler could identify an allocator class. Just adding "deduced" to the signature entry for it doesn't work. Are we stuck with the tag for that one? Almost, but there's a way around the problem, using is_class from Boost.TypeTraits: using boost::parameter::optional; using boost::parameter::deduced; using boost::mpl::_; using boost::is_base_of; using boost::is_class; typedef boost::parameter::parameters< optional, is_base_of >, optional, is_base_of >, optional, is_base_of >, optional, is_class<_> > > integer_signature; How, you may ask, could that possibly work? Since we had to use wrapper classes for the value types, *all* of the types are classes! It works because the allocator is the only parameter with this problem, and is listed last in the signature. Boost.Parameter evaluates the parameters in the order that they're listed, and after filtering out the other parameters based on their wrapper types, the allocator is the only one left. It means that *any* class (other than the specified wrappers) would be taken as an allocator, but that's not really a problem. If you tried using a non-allocator... int main() { integer_t< std::ostringstream, // Error, not an allocator negatives_t, fixedlength_t<128> >::report(); return 0; } ...the compiler would complain about the problem elsewhere in the code, because the type you specified doesn't provide the functions and typedefs that an allocator must. So we've now achieved our third and final goal: eliminating unnecessary syntactic noise. 2.5.5 Conclusion This may seem like a lot of work, but there are several advantages. Code that uses the class is pretty much self-documenting now, which makes it easier to read and understand, and harder for bugs to hide in it. The person using the code can take full advantage of the default parameters, regardless of where they appear. And when, as is inevitable, the requirements change and you have to modify or add to the class's template parameters, you don't have to change every type definition of the class in the program. These advantages get even more compelling as the parameter list gets longer, or as more combinations of parameters are used.