Francis Cugler
Francis Cugler

Reputation: 7925

Specifying the requirements for class template arguments

I have few classes that look like this:

struct neg_inf {
    constexpr double operator()() { return -std::numeric_limits<double>::infinity(); }
};

struct pos_inf {
    constexpr double operator()() { return std::numeric_limits<double>::infinity(); }
};

template<typename dX, class LowerBound, class UpperBound>
class limit {
    dX dx;
    UpperBound upperBound;
    LowerBound lowerBound;
    double step_size;

    limit( dX x, LowerBound lower, UpperBound upper, double step = 1 ) :
        dx{ x }, lowerBound{ lower }, upperBound{ upper }, step_size{ step }
    {}

    dX value() const { return dx; }
    LowerBound lower() const { return lowerBound; }
    UpperBound upper() const { return upperBound; }
    double step() const { return step_size; }            
};

These classes so far work as intended. Now I want to modify the template arguments using conditionals such as std::enable_if_t, std::is_arithemtic std::is_same.

These are the conditions that need to be met to instantiate a limit object.

  • dX must be at least arithmetic and numerical
  • Lower & Upper bound must be either numerical, arithmetic or neg_inf or pos_inf.

For example these are valid instantiations:

dX = 1st and can be any: int, long, float, double, /*complex*/, etc.

limit< 1st, 1st, 1st >     // default template

limit< 1st, 1st, pos_inf >  // these will be specializations
limit< 1st, 1st, neg_inf >
limit< 1st, pos_inf, 1st >
limit< 1st, neg_inf, 1st >
limit< 1st, pos_inf, pos_inf >
limit< 1st, neg_inf, neg_inf >
limit< 1st, neg_inf, pos_inf >
limit< 1st, pos_inf, neg_inf >

These are the valid conditions for instantiating my template. I am planning on partially specializing this class when either or both UpperBound and or LowerBound are an infinity type. When the upper and lower bounds are numerical - arithmetic types, the general or default template will handle them.

My question is what would the template declaration to my class look like with the type_traits library?

Upvotes: 2

Views: 102

Answers (2)

serkan.tuerker
serkan.tuerker

Reputation: 1819

Witht the upcoming C++20 standard, we will get concepts and constraints. With that in mind, we can declare our own concepts and get rid of SFINAE.

#include <limits>
#include <type_traits>

struct neg_inf {
    constexpr double operator()() { return -std::numeric_limits<double>::infinity(); }
};

struct pos_inf {
    constexpr double operator()() { return std::numeric_limits<double>::infinity(); }
};

template<typename T>
concept Arithmetic = std::is_arithmetic_v<T>;

template<typename T>
concept Bound = std::is_arithmetic_v<T>     || 
                std::is_same_v<T, neg_inf>  ||
                std::is_same_v<T, pos_inf>;

template<Arithmetic dX, Bound LowerBound, Bound UpperBound>
class limit {
 private:   
    dX dx;
    UpperBound upperBound;
    LowerBound lowerBound;
    double step_size;

public:
    limit() = default;
    limit( dX x, LowerBound lower, UpperBound upper, double step = 1 ) :
        dx{ x }, lowerBound{ lower }, upperBound{ upper }, step_size{ step }
    {}

    dX value() const { return dx; }
    LowerBound lower() const { return lowerBound; }
    UpperBound upper() const { return upperBound; }
    double step() const { return step_size; }            
};

LIVE DEMO

Upvotes: 2

Jarod42
Jarod42

Reputation: 218278

One way to restrict template type to class is to add extra parameter for SFINAE:

template <typename dX, class LowerBound, class UpperBound, typename Enabler = void>
class limit;

and then, provide specialization with appropriate SFINAE

template <typename dX, class LowerBound, class UpperBound>
class limit<dX,
            LowerBound,
            UpperBound,
            std::enable_if_t<my_condition<dX, LowerBound, UpperBound>::value>>
{
    // ...
};

So in your case, my_condition should be something like

template <typename dX, class LowerBound, class UpperBound>
using my_condition =
    std::conjunction<std::is_arithmetic<dX>,
                     std::disjunction<std::is_arithmetic<LowerBound>,
                                      std::is_same<LowerBound, neg_inf>,
                                      std::is_same<LowerBound, pos_inf>>,
                      std::disjunction<std::is_arithmetic<UpperBound>,
                                       std::is_same<UpperBound, neg_inf>,
                                       std::is_same<UpperBound, pos_inf>>
                      >;

An other way, is static_assert:

template <typename dX, class LowerBound, class UpperBound>
class limit
{
    static_assert(std::is_arithmetic<dX>::value, "!");
    static_assert(std::is_arithmetic<LowerBound>::value
                  || std::is_same<LowerBound, neg_inf>::value
                  || std::is_same<LowerBound, pos_inf>::value, "!");
    static_assert(std::is_arithmetic<UpperBound>::value
                  || std::is_same<UpperBound, neg_inf>::value
                  || std::is_same<UpperBound, pos_inf>::value, "!");
    // ...
};

Upvotes: 2

Related Questions