c++templatesgeneric-programmingtemplate-classesboost-units

Why would a template class have an unused type?


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?


Solution

  • 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.