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?!…
I don’t think you should need memset, because you can zero-initialize PODs just as you can explicitly invoke the default constructor of non-PODs. (unless I’m terribly mistaken).
In your quest to determine the pod-ness of a type, you might also take into account this note from boost::is_pod reference:
(I think boost::type_traits are making it into the standard library in C++0x, and in such a case it would be reasonable to expect an
is_podthat actually works.)But if you want to specialize based on a condition, you can introduce a bool parameter. E.g something like this:
Here’s also something that works sort of like you might be imagining (compiles at least with MinGW 4.4 and VC++ 2005 – the latter also nicely produces a warning that the array will be zero-initialized! :)).
This uses a default boolean argument which you probably shouldn’t ever specify yourself.
However, Initialized will probably never be as good as the real thing. One short-coming is shown in the test code: it can interfere with template type deduction. Also, for arrays you’ll have a choice: if you want to zero-initialize it with the constructor, then you can’t have non-default array initialization.
If the usage is that you are going to track down all uninitialized variables and wrap them into Initialized, I’m not quite sure why you won’t just initialized them yourself.
Also, for tracking down uninitialized variables, perhaps compiler warnings can help a lot.