Class wrapping

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.

Implementation

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;

Example

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;
}

Files

Class wrapping

Testing for class wrapping