I'm reviewing the boost units library and I am puzzled why the boost::units::unit class has an extra template parameter. Here is the example:
http://www.boost.org/doc/libs/1_57_0/boost/units/unit.hpp
template<class Dim,class System, class Enable>
class unit
{
public:
typedef unit<Dim, System> unit_type;
typedef unit<Dim,System> this_type;
typedef Dim dimension_type;
typedef System system_type;
unit() { }
unit(const this_type&) { }
//~unit() { }
this_type& operator=(const this_type&) { return *this; }
// sun will ignore errors resulting from templates
// instantiated in the return type of a function.
// Make sure that we get an error anyway by putting.
// the check in the destructor.
#ifdef __SUNPRO_CC
~unit() {
BOOST_MPL_ASSERT((detail::check_system<System, Dim>));
BOOST_MPL_ASSERT((is_dimension_list<Dim>));
}
#else
private:
BOOST_MPL_ASSERT((detail::check_system<System, Dim>));
BOOST_MPL_ASSERT((is_dimension_list<Dim>));
#endif
};
The class is used to add dimensions to dimension system.
typedef unit<pressure_dimension,si::system> pressure;
What purpose would "Enable" serve in this case?
That class template is forward declared in "units_fwd.hpp"
.
template<class Dim,class System, class Enable=void> class unit;
there we see that the 3rd parameter is defaulted to void
. I would be tempted to even do template<class Dim,class System, class=void> class unit;
and also leave it nameless in the implementation, but there may be compiler compatibility or code standards that mean they give it the name Enable
.
Such an "extra" type allows for SFINAE tests on the first two types. If you create a specialization where the 3rd type is only valid for some subsets of Dim
and System
the extra parameter (defaulted to void
) lets you do that.
You can even leave your base implementation empty (either ;
or {};
which have different effects) and only specialize, making arguments that fail your test be treated as invalid options.
Here is a toy example of SFINAE:
template<class T, class=void> struct is_int : std::false_type {};
template<class T> struct is_int< T,
std::enable_if_t< std::is_same<T, int>{} >
> : std::true_type {};
the base specialization inherits from false_type
. If, however, the type T
passes my test in the next specialization, it is preferred, and the result inherits from true_type
.
In this case, we'd be better served by just doing is_int< int, void >:std::true_type{}
, but in a more complex situation (say, is_integral
) we can do a nearly arbitrary compile-time check and use it to enable or disable the true_type
specialization.