Search code examples
c++c++11functortemplate-specializationenable-if

Conditional specialization of functors operator()


I would like to ask you for help with the programming headache I face last few days. Let me try to explain what I am about to implement...

My goal is to define a set of equations with its validity. Let me explain more in detail...

I think about being each equation object a functor - a class defining the operator(). The definition of this operator should be specialized for each equation type. The specialization contains the calculation itself:

.h:

enum class IDs : int { A = 0, B = 1, C = 2 };

template<IDs WHICH>
struct Equation
{
    int operator() ( void );
}

.cpp:

template<>
Equation<IDs::A>::operator()( void )
{
    /* Just some sample equation */
    return( 42 + 28 );
}

As you may have noticed, the specialization is defined by enum class member IDs::?.

This seems to be working. But I would like to add so called availability feature - The equation may be valid only for certain user object types.

There is 'validity group' declared:
/* Object types declaration */
namespace Objects {
    using Object0 = boost::mpl::int_<0>;
    using Object1 = boost::mpl::int_<1>;
    using Object2 = boost::mpl::int_<2>;
}
/* Validity groups declaration */
using ValidityGroup1 = boost::mpl::vector<Object0, Object2>;
using ValidityGroup2 = boost::mpl::vector<Object1>;

I am using the following construct to make the class enabled or disabled (using boost::enable_if). Just to show how I use it:

template<typename TYPE_LIST, typename QUERY_TYPE>
struct IsTypeInList
{
    using TypePos    = typename boost::mpl::find<TYPE_LIST, QUERY_TYPE>::type;
    using Finish     = typename boost::mpl::end<TYPE_LIST>::type;
    using type       = typename boost::mpl::not_<boost::is_same<TypePos, Finish> >::type;
    using value_type = typename type::value_type;

    static const bool value = type::value;
};

template<typename OBJECT_TYPE, typename ENABLER=void>
class SampleClass;

template<typename OBJECT_TYPE>
class SampleClass<OBJECT_TYPE, typename boost::enable_if<typename IsTypeInList<ValidityGroup1, Object0>::type>::type>
{}

The partial specialization of SampleClass is available only if Object0 belongs to ValidityGroup1. So far so good. This principle is verified.

Now the funny stuff comes. I would like to merge the two things together:

THE GOAL:

Define Equation's operator() who's specialization containing valid body is defined by IDs::?? enum class value" and is available only for Object belonging to ValidityGroup... There can be another calculation of the same IDs::?? but valid for Object in other ValidityGroup (aka Object0's property is calculated some other way than for Object1)

I know the whole concept is quite complicated and may be confusing. Let me show my attempt to implement this stuff:

template<typename OBJECT_TYPE, typename VALIDITY_GROUP, IDs ID, typename ENABLER = void>
class Equation;

template<typename OBJECT_TYPE, typename VALIDITY_GROUP, IDs ID>
class Equation<OBJECT_TYPE, VALIDITY_GROUP, ID, typename boost::enable_if<typename IsTypeInList<VALIDITY_GROUP, OBJECT_TYPE>::type>::type >
:   public EquationBase<IDs>
{
public:
int operator() ( void );
};

template<typename OBJECT_TYPE, typename VALIDITY_GROUP, IDs ID>
int Equation<OBJECT_TYPE, ValidityGroup1, Ids::A>::operator() ( void )
{
    return( 42 + 56 );
}

But the operator() definition is not working... Could you please advise me how to make this working? Or does anyone have any other idea how to fulfill the goal written above?

Many thanks in advance to anybody willing to help me...

Cheers Martin

EDIT: The equation is used in template class object. Let the code explain:

template<typename OBJECT_TYPE>
class Object
{
public:
    Object( void );
};

.cpp:

template<typename OBJECT_TYPE>
Object<OBJECT_TYPE>::Object( void )
{
    std::cout << Equation<IDs::A>()() << std::endl;
}

The problem is OBJECT_TYPE is not defined when the operators () are specialized...


Solution

  • If I understand correctly what you want to obtain, I suppose there are many ways.

    The following is a iper-semplified example (but complete ad working) that show how to select different implementations using std::enable_if (but boost::enable_if should be OK) with the return type of the operator

    #include <iostream>
    #include <type_traits>
    
    template <typename ObjT, typename ValT>
    class Equation
     {
       public:
          template <typename X = ObjT>
             typename std::enable_if<true == std::is_same<X, ValT>::value, int>::type
             operator() ( void )
              { return( 0 ); }
    
          template <typename X = ObjT>
             typename std::enable_if<false == std::is_same<X, ValT>::value, int>::type
             operator() ( void )
              { return( 1 ); }
     };
    
    int main()
     {
       Equation<int, int>  eq0;
       Equation<int, long> eq1;
    
       std::cout << "eq0 val: " << eq0() << std::endl; // print "eq0 val: 0"
       std::cout << "eq1 val: " << eq1() << std::endl; // print "eq1 val: 1"
     }
    

    Not really elegant, I suppose.

    Another solution (that, I suppose, best fits your desiderata) could be the following based on class partial specialization

    #include <iostream>
    #include <type_traits>
    
    
    template <typename ObjT, typename ValT, bool = std::is_same<ObjT, ValT>::value>
    class Equation;
    
    template <typename ObjT, typename ValT>
    class Equation<ObjT, ValT, true>
     {
       public:
          int operator() ();
     };
    
    template <typename ObjT, typename ValT>
    class Equation<ObjT, ValT, false>
     {
       public:
          int operator() ();
     };
    
    
    template <typename ObjT, typename ValT>
    int Equation<ObjT, ValT, true>::operator() ()
     { return( 0 ); }
    
    template <typename ObjT, typename ValT>
    int Equation<ObjT, ValT, false>::operator() ()
     { return( 1 ); }
    
    int main()
     {
       Equation<int, int>  eq0;
       Equation<int, long> eq1;
    
       std::cout << "eq0 val: " << eq0() << std::endl; // print "eq0 val: 0"
       std::cout << "eq1 val: " << eq1() << std::endl; // print "eq1 val: 1"
     }