Reputation: 9733
I've got some code here which uses special values to check if a variable is initialized.
#define NULLVALUE_FLOAT -9999
#define NULLVALUE_INT -9999
#define NULLVALUE_UINT 0
#define NULLVALUE_DOUBLE -9999
#define NULLVALUE_LONG -9999
#define NULLVALUE_LONGLONG -9999
#define NULLVALUE_CSTRING ""
bool isNull(float value) { return NULLVALUE_FLOAT == value; }
bool isNull(double value); ...
bool isNull(long value);...
bool isNull(int value);...
bool isNull(UINT value);...
bool isNull(CString value);...
So unsigned's special value is zero, signed ints and floating point is -9999 and string is the empty string.
Now I would like to rewrite this into one template function, but having problems with it because of the CString version. The best I can do is two functions, one for fundamental types and one for CString.
template<typename T,
bool E = std::is_fundamental<T>::value && !std::is_unsigned<T>::value && (std::is_floating_point<T>::value || std::is_integral<T>::value),
bool E2 = std::is_fundamental<T>::value && std::is_unsigned<T>::value>
bool isNull(const T &t)
{
if (E)
return t == -9999;
else if (E2)
return t == 0;
else
return false;
}
bool isNull(const CString & s)
{
return s == "";
}
Is it possible to get this working with only one function, even when adding other classes with default values?
Upvotes: 0
Views: 87
Reputation: 9733
Now that C++17 is out for visual studio this can be solved rather easy with its "if constexpr" feature. Let the compiler generate the functions as needed.
template<typename T>
bool isDefault(const T &t)
{
if constexpr (std::is_unsigned<T>::value)
{
return t == 0;
}
else if constexpr (std::is_floating_point<T>::value || std::is_integral<T>::value)
{
return t == -9999;
}
else if constexpr (std::is_same<T, CString>::value)
{
return t == "";
}
else if constexpr (std::is_same<T, COleDateTime>::value)
{
return t.GetStatus() == COleDateTime::null;
}
}
Upvotes: 0
Reputation: 170064
The solution presents itself rather easily if you shift your focus away from defining the function, and onto defining the way to name Null values.
Add a traits class that exposes a value
member. Specialize that however you find convenient.
template<typename T, typename = void>
struct NullValueHolder{};
template<typename T>
constexpr decltype(NullValueHolder<T>::value) NullValue = NullValueHolder<T>::value;
// Utility for easy referral
template<typename T>
struct NullValueHolder<T, std::enable_if_t<std::is_arithmetic<T>::value && std::is_signed<T>::value>> {
static constexpr T value = -9999;
};
template<typename T>
struct NullValueHolder<T, std::enable_if_t<std::is_unsigned<T>::value>> {
static constexpr T value = 0;
};
template<>
struct NullValueHolder<CString, void> {
static constexpr const char * value = "";
};
The above makes heavy use of SFINAE. The void
in the primary template must be matched by a specialization in order for that specialization to be picked. Each enable_if_t
provides that void
if the conditions are met.
Now the function writes itself:
template<typename T>
bool isNull(T const& t) { return t == NullValue<T>; }
Upvotes: 5