I\'m creating a database access layer in native C++, and I\'m looking at ways to support NULL values. Here is what I have so far:
class CNullValue
{
public:
EDIT: Improved with throw exception on "null" Value. More fixes
If boost is not an option, in c++11 you can also take advantage of nullptr and the nullptr_t typedef to create a Nullable with pretty much same semantics as .NET one.
#pragma once
#include
#include
template
class Nullable final
{
public:
Nullable();
Nullable(const T &value);
Nullable(std::nullptr_t nullpointer);
const Nullable & operator=(const Nullable &value);
const Nullable & operator=(const T &value);
const Nullable & operator=(std::nullptr_t nullpointer);
bool HasValue() const;
const T & GetValueOrDefault() const;
const T & GetValueOrDefault(const T &def) const;
bool TryGetValue(T &value) const;
T * operator->();
const T * operator->() const;
T & operator*();
const T & operator*() const;
public:
class NullableValue final
{
public:
friend class Nullable;
private:
NullableValue();
NullableValue(const T &value);
public:
NullableValue & operator=(const NullableValue &) = delete;
operator const T &() const;
const T & operator*() const;
const T * operator&() const;
// https://stackoverflow.com/questions/42183631/inability-to-overload-dot-operator-in-c
const T * operator->() const;
public:
template
friend bool operator==(const Nullable &op1, const Nullable &op2);
template
friend bool operator==(const Nullable &op, const T2 &value);
template
friend bool operator==(const T2 &value, const Nullable &op);
template
friend bool operator==(const Nullable &op, std::nullptr_t nullpointer);
template
friend bool operator!=(const Nullable &op1, const Nullable &op2);
template
friend bool operator!=(const Nullable &op, const T2 &value);
template
friend bool operator!=(const T2 &value, const Nullable &op);
template
friend bool operator==(std::nullptr_t nullpointer, const Nullable &op);
template
friend bool operator!=(const Nullable &op, std::nullptr_t nullpointer);
template
friend bool operator!=(std::nullptr_t nullpointer, const Nullable &op);
private:
void checkHasValue() const;
private:
bool m_hasValue;
T m_value;
};
public:
NullableValue Value;
};
template
Nullable::NullableValue::NullableValue()
: m_hasValue(false), m_value(T()) { }
template
Nullable::NullableValue::NullableValue(const T &value)
: m_hasValue(true), m_value(value) { }
template
Nullable::NullableValue::operator const T &() const
{
checkHasValue();
return m_value;
}
template
const T & Nullable::NullableValue::operator*() const
{
checkHasValue();
return m_value;
}
template
const T * Nullable::NullableValue::operator&() const
{
checkHasValue();
return &m_value;
}
template
const T * Nullable::NullableValue::operator->() const
{
checkHasValue();
return &m_value;
}
template
void Nullable::NullableValue::checkHasValue() const
{
if (!m_hasValue)
throw std::runtime_error("Nullable object must have a value");
}
template
bool Nullable::HasValue() const { return Value.m_hasValue; }
template
const T & Nullable::GetValueOrDefault() const
{
return Value.m_value;
}
template
const T & Nullable::GetValueOrDefault(const T &def) const
{
if (Value.m_hasValue)
return Value.m_value;
else
return def;
}
template
bool Nullable::TryGetValue(T &value) const
{
value = Value.m_value;
return Value.m_hasValue;
}
template
Nullable::Nullable() { }
template
Nullable::Nullable(std::nullptr_t nullpointer) { (void)nullpointer; }
template
Nullable::Nullable(const T &value)
: Value(value) { }
template
bool operator==(const Nullable &op1, const Nullable &op2)
{
if (op1.Value.m_hasValue != op2.Value.m_hasValue)
return false;
if (op1.Value.m_hasValue)
return op1.Value.m_value == op2.Value.m_value;
else
return true;
}
template
bool operator==(const Nullable &op, const T2 &value)
{
if (!op.Value.m_hasValue)
return false;
return op.Value.m_value == value;
}
template
bool operator==(const T2 &value, const Nullable &op)
{
if (!op.Value.m_hasValue)
return false;
return op.Value.m_value == value;
}
template
bool operator==(const Nullable &op, std::nullptr_t nullpointer)
{
(void)nullpointer;
return !op.Value.m_hasValue;
}
template
bool operator==(std::nullptr_t nullpointer, const Nullable &op)
{
(void)nullpointer;
return !op.Value.m_hasValue;
}
template
bool operator!=(const Nullable &op1, const Nullable &op2)
{
if (op1.Value.m_hasValue != op2.Value.m_hasValue)
return true;
if (op1.Value.m_hasValue)
return op1.Value.m_value != op2.Value.m_value;
else
return false;
}
template
bool operator!=(const Nullable &op, const T2 &value)
{
if (!op.Value.m_hasValue)
return true;
return op.Value.m_value != value;
}
template
bool operator!=(const T2 &value, const Nullable &op)
{
if (!op.Value.m_hasValue)
return false;
return op.Value.m_value != value;
}
template
bool operator!=(const Nullable &op, std::nullptr_t nullpointer)
{
(void)nullpointer;
return op.Value.m_hasValue;
}
template
bool operator!=(std::nullptr_t nullpointer, const Nullable &op)
{
(void)nullpointer;
return op.Value.m_hasValue;
}
template
const Nullable & Nullable::operator=(const Nullable &value)
{
Value.m_hasValue = value.Value.m_hasValue;
Value.m_value = value.Value.m_value;
return *this;
}
template
const Nullable & Nullable::operator=(const T &value)
{
Value.m_hasValue = true;
Value.m_value = value;
return *this;
}
template
const Nullable & Nullable::operator=(std::nullptr_t nullpointer)
{
(void)nullpointer;
Value.m_hasValue = false;
Value.m_value = T();
return *this;
}
template
T * Nullable::operator->()
{
return &Value.m_value;
}
template
const T * Nullable::operator->() const
{
return &Value.m_value;
}
template
T & Nullable::operator*()
{
return Value.m_value;
}
template
const T & Nullable::operator*() const
{
return Value.m_value;
}
I tested it in gcc, clang and VS15 with the following:
#include
using namespace std;
int main(int argc, char* argv[])
{
(void)argc;
(void)argv;
Nullable ni1;
Nullable ni2 = nullptr;
Nullable ni3 = 3;
Nullable ni4 = 4;
ni4 = nullptr;
Nullable ni5 = 5;
Nullable ni6;
ni6 = ni3;
Nullable ni7(ni3);
//Nullable ni8 = NULL; // This is an error in gcc/clang but it's ok in VS12
cout << (ni1 == nullptr ? "True" : "False") << endl; // True
cout << (ni2 == nullptr ? "True" : "False") << endl; // True
cout << (ni2 == 3 ? "True" : "False") << endl; // False
cout << (ni2 == ni3 ? "True" : "False") << endl; // False
cout << (ni3 == 3 ? "True" : "False") << endl; // True
cout << (ni2 == ni4 ? "True" : "False") << endl; // True
cout << (ni3 == ni5 ? "True" : "False") << endl; // False
cout << (ni3 == ni6 ? "True" : "False") << endl; // True
cout << (ni3 == ni7 ? "True" : "False") << endl; // True
//cout << ni1 << endl; // Doesn't compile
//cout << ni3 << endl; // Doesn't compile
cout << ni3.Value << endl; // 3
//cout << ni1.Value << endl; // Throw exception
//cout << ni2.Value << endl; // Throw exception
//ni3.Value = 2; // Doesn't compile
cout << sizeof(ni1) << endl; // 8 on VS15
return 0;
}