One option for delayed/optional construction of the value could be:
template <typename T>
class ListNode
{
private:
ListNode<T>* p_mNextNode;
ListNode<T>* p_mPreviousNode;
union {
char dummy;
T mNodeVal;
};
ListNode() :
p_mNextNode(0),
p_mPreviousNode(0),
dummy() {}
ListNode(T const & aVal) :
p_mNextNode(0),
p_mPreviousNode(0),
mNodeVal(aVal) {}
...
};
You will need to explicitly call its destructor, however, since the compiler no longer knows which of the variant members is active.
But it's better to do this for the dummy ListNode
inside the List
. I guess that the implementation with ::operator new
was also special-casing the dummy. Something like this:
template< typename T >
class List
{
private:
class ListNode
{
private:
ListNode* p_mNextNode;
ListNode* p_mPreviousNode;
T mNodeVal;
};
class DummyListNode
{
public:
ListNode* p_mNextNode;
ListNode* p_mPreviousNode;
};
These are both "standard-layout", and by 9.2:
If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them.
Let's take advantage of that now:
union {
DummyListNode dummy_head_tail;
ListNode head_tail
};
// ... iterators and stuff, using &head_tail as the first and last ListNode*
public:
List() : dummy_head_tail{ nullptr, nullptr } { }
~List() { /* free all nodes, then */ dummy_head_tail->~DummyListNode(); }
};