Back to Programming techniques
Class wrapping is a technique for transparently indirecting operations to a native type through a class type. This is useful in the implementation of a data structure which supports embedding user-data in its parts. First, deriving from the wrapped type automatically makes use of the empty base-class optimization, so that memory-use can be completely avoided when user-data is not needed. Second, being able to derive from arbitrary types makes it possible to fuse several layers of user-data and data-structure-specific data seamlessly together.
The idea behind class wrapping is best demonstrated by its actual implementation:
template <typename Type>
class Class
{
public:
PASTEL_STATIC_ASSERT(std::is_scalar<Type>::value);
Class()
: member_()
{
}
Class(Type that)
: member_(that)
{
}
operator Type&()
{
return member_;
}
operator const Type&() const
{
return member_;
}
private:
Type member_;
};
template <>
class Class<void>
: boost::less_than_comparable<Class<void>,
boost::equality_comparable<Class<void>
> >
{
public:
Class()
{
}
Class(std::nullptr_t)
{
}
bool operator<(const Class& that) const
{
return false;
}
bool operator==(const Class& that)
{
return true;
}
};
Trivial comparison functions, and a trivial hash function, are defined for the void-wrapper. This way Class<void>
works, for example, as a key in the red-black tree (it is useful!). A void-wrapper can only be default-constructed, or constructed from a null-pointer. This way nullptr
can be passed, for example, in place of the key in a red-black tree. A redirecting hash function is defined for the non-void wrappers. We allow wrapping only for scalar types, and the void type, since there is no advantage in doing so for the other types. Correspondingly, we provide a way to wrap a type conditionally as follows:
template <typename Type>
using As_Class =
typename std::conditional<
std::is_scalar<Type>::value ||
std::is_void<Type>::value,
Class<Type>, Type
>::type;
An example is given by a generic graph data structure which allows to embed user-defined labels into the edges and vertices. In case the user data-type (say, for the edges) is void
, no memory should be allocated for the user data. Using the class-wrapping pattern in this section, this would be implemented similarly to the following.
#include "pastel/sys/generic/class.h"
using namespace Pastel;
class Vertex {};
template <typename EdgeData>
class Edge
: public As_Class<EdgeData>
{
public:
using Base = As_Class<EdgeData>;
Edge& operator=(Edge edge) = delete;
Vertex* from() {return from_;}
Vertex* to() {return to_;}
//! Assigns to the contained data.
template <typename Type>
Edge& operator=(Type&& that)
{
((Base&)*this) = std::forward<Type>(that);
return *this;
}
private:
Vertex* from_;
Vertex* to_;
};
class Label
{
public:
int label;
};
int main()
{
Edge<void> a;
a;
a.from();
Edge<int> b;
b = 5;
b.to();
Edge<Label> c;
c.label = 5;
c.from();
return 0;
}