Reputation: 7925
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 leastarithmetic
andnumerical
Lower
&Upper
bound
must be eithernumerical
,arithmetic
orneg_inf
orpos_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
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; }
};
Upvotes: 2
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