Serve Laurijssen
Serve Laurijssen

Reputation: 9733

function template for default values

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

Answers (2)

Serve Laurijssen
Serve Laurijssen

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

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

Related Questions