We use boost - so using that library should be fine.
But I've never managed to wrap my head around creating a set of templates which give you the right specialization for a whole class of data types, as opposed to specializing for a single data type (which I know how to do).
Let me go for an example to try to bring this down to earth. I want to have a set of classes which can be used as:
Initialized<T> t;
Where T is either a simple basic type, a PODS, or an array. It cannot be a class, for a class is expected to have its own constructor, and overwriting its raw memory is a terrible idea.
Initialized should basically memset(&t, 0, sizeof(t)); It makes it easier to ensure that runtime code is not different from debug code when dealing with legacy structs.
Initialized where SDT = simple data type, should simply create a struct which wrappers the underlying SDT and uses the compilers t() to generate the compiler defined default constructor for that type (it could amount to a memset as well, though it seems more elegant to simply result in t().
Here's a stab at it, using Initialized<> for PODs, and Initialised<> for SDTs:
// zeroed out PODS (not array)
// usage: Initialized<RECT> r;
template <typename T>
struct Initialized : public T
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
// publish our underlying data type
typedef T DataType;
// default (initialized) ctor
Initialized() { Reset(); }
// reset
void Reset() { Zero((T&)(*this)); }
// auto-conversion ctor
template <typename OtherType> Initialized(const OtherType & t) : T(t) { }
// auto-conversion assignment
template <typename OtherType> Initialized<DataType> & operator = (const OtherType & t) { *this = t; }
};
And for SDTs:
// Initialised for simple data types - results in compiler generated default ctor
template <typename T>
struct Initialised
{
// default valued construction
Initialised() : m_value() { }
// implicit valued construction (auto-conversion)
template <typename U> Initialised(const U & rhs) : m_value(rhs) { }
// assignment
template <typename U> T & operator = (const U & rhs) { if ((void*)&m_value != (void*)&rhs) m_value = rhs; return *this; }
// implicit conversion to the underlying type
operator T & () { return m_value; }
operator const T & () const { return m_value; }
// the data
T m_value;
};
I have specialized Initialised for T*, to provide natural pointer behaviors. And I have an InitializedArray<> for arrays, which takes both the element type and array-size as template arguments. But again, I have to use template name to distinguish - I don't grok MPL well enough to provide a single template that results in the correct specialization at compile time all from a single name (Initialized<>, ideally).
I would love also to be able to provide an overloaded Initialized<typename T, T init_value> as well, so that for non-scalar values the user could define the default initialization value (or memset value)
I apologize for asking something that may take a bit of effort to answer. This seems to be a hurdle that I haven't been able to overcome in my own MPL reading on my own, but perhaps with some of your help I may be able to nail this functionality down!
Based on Uncle Ben's answer(s) below, I tried the following:
// containment implementation
template <typename T, bool bIsInheritable = false>
struct InitializedImpl
{
// publish our underlying data type
typedef T DataType;
// auto-zero construction
InitializedImpl() : m_value() { }
// auto-conversion constructor
template <typename U> InitializedImpl(const U & rhs) : m_value(rhs) { }
// auto-conversion assignment
template <typename U> T & operator = (const U & rhs) { if ((void*)&m_value != (void*)&rhs) m_value = rhs; return *this; }
// implicit conversion to the underlying type
operator T & () { return m_value; }
operator const T & () const { return m_value; }
// the data
T m_value;
};
// inheritance implementation
template <typename T>
struct InitializedImpl<T,true> : public T
{
// publish our underlying data type
typedef T DataType;
// auto-zero ctor
InitializedImpl() : T() { }
// auto-conversion ctor
template <typename OtherType> InitializedImpl(const OtherType & t) : T(t) { }
// auto-conversion assignment
template <typename OtherType> InitializedImpl<DataType> & operator = (const OtherType & t) { *this = t; }
};
// attempt to use type-traits to select the correct implementation for T
template <typename T>
struct Initialized : public InitializedImpl<T, boost::is_class<T>::value>
{
// publish our underlying data type
typedef T DataType;
};
And then tried a couple of usage tests.
int main()
{
Initialized<int> i;
ASSERT(i == 0);
i = 9; // <- ERROR
}
This results in an error: *binary '=' : no operator found which takes a right-hand operand of type 'InitializedImpl ' (or there is no acceptable conversion)
Whereas if I directly instantiate the correct base type (instead of a derived type):
int main()
{
InitializedImpl<int,false> i;
ASSERT(i == 0);
i = 9; // <- OK
}
Now I can use i as any old int. Which is what I want!
The exact same problems arise if I try to do the same for structs:
int main()
{
Initialized<RECT> r;
ASSERT(r.left == 0); // <- it does let me access r's members correctly! :)
RECT r1;
r = r1; // <- ERROR
InitializedImpl<RECT,true> r2;
r2 = r1; // OK
}
So, as you can see, I need some way to tell the compiler to promote an Initialized to act like a true T.
If C++ let me inherit from basic types, I could just use the inheritance technique and all would be well.
Or if I had some way to tell the compiler to extrapolate all methods in parent to child, so that anything valid on parent was valid on child, I'd be okay.
Or if I could use MPL or type-traits to typedef instead of inherit what I need, then there would be no child class, and no propagation issue.
Ideas?!...