I'm trying to learn about templates and template specialisation. I'm writing a template class for arrays, using template specialisation to avoid code bloat. Thus I have a fully specialised template MyArray and then I inherit from this one like class MyArray<T*> : private MyArray<void*>. I'm having trouble overloading the subscript operators (one for non-const refs, one for const refs). Here's a piece of code (far from complete, but contains my problem).
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
/*** template class MyArray **/
template <class T>
class MyArray {};
/*** Full template specialization for MyArray holding pointers ***/
template <>
class MyArray<void*> {
public:
explicit MyArray(unsigned s = 100) : sz(s) {
data = new void*[s];
}
virtual ~MyArray() { delete[] data; }
/** subscript operator overload for non-const refs **/
void*& operator[](unsigned i) {
return data[i];
}
/** subscript operator overload for const refs **/
const void*& operator[](unsigned i) const {
return data[i]; // line 26
}
unsigned size() const { return sz; }
private:
void** data;
unsigned sz;
};
/** Partial specialization: create the template class by inheriting from the one above **/
template <class T>
class MyArray<T*> : private MyArray<void*> {
public:
explicit MyArray(unsigned s = 100) : MyArray<void*>::MyArray(s) {
data = new T*[s];
}
virtual ~MyArray() { delete[] data; }
/** subscript operator overload for non-const refs **/
T*& operator[](unsigned i) {
return reinterpret_cast<T*&>( // line 47
MyArray<void*>::operator[](i)
);
}
/** subscript operator overload for const refs **/
const T*& operator[](unsigned i) const {
return reinterpret_cast<const T*&>(
MyArray<void*>::operator[](i)
);
}
unsigned size() const { return MyArray<void*>::size(); }
private:
T** data;
};
/** input function for filling MyArray's **/
template <class T>
void InputMyArray(MyArray<T*>& my_array) {
unsigned size = 0;
T tmp;
while (cin >> tmp) {
T* i = new T;
*i = tmp;
my_array[size++] = i;
}
}
/** output function for printing elements of MyArray's **/
template <class T>
void OutputArray(const MyArray<T*>& my_array) {
for (unsigned i = 0; i < my_array.size(); i++) {
cout << *my_array[i] << " ";
}
cout << endl;
}
int main() {
/** Initialize array, fill it, print contents **/
MyArray<int*> p;
InputMyArray(p);
cout << "MyArray of pointer to ints holds int values: " << endl;
OutputArray(p);
return 0;
}
The compiler (clang) is complaining (error) about line 26
non-const lvalue reference to type 'const void *' cannot bind to a value of unrelated type 'void *'
I suppose I don't want the compiler to interpret this as a non-const reference - what I want is for it to be const. How can I properly overload this operator in this context? The corresponding piece of code works fine for a template class without specialisation, like MyArray<T>.
The compiler further complains (warnings) about the reinterpret_casts which apparently contain undefined behaviour
reinterpret_cast from 'void *' to 'int *&' has undefined behavior
(line 47). The reinterpret_casts are essentially copy-pasted from my instructions, so I would think they are to be used like this. I don't know why the reference to void* isn't picked up. What am I doing wrong here?
Your template is a container for void*, not const void*:
/** subscript operator overload for const refs **/
void* const& operator[](unsigned i) const {
return data[i]; // line 26
}
Same for T*:
/** subscript operator overload for const refs **/
T* const& operator[](unsigned i) const {
return reinterpret_cast<T*const&>(
MyArray<void*>::operator[](i)
);
}
Related
hello i want to create simple array class to get value and insert value like (array[0] = 4) and this is my program but i have problem to use [] = in same time for insert
template <typename Param>
class arr
{
private:
int Last_point = 0;
Param Data[];
public:
void& operator[]=(int Element_id, Param v)
{
Data[Element_id] = v;
}
Param& operator[] (int Element_id)
{
return Data[Element_id];
}
};
void main()
{
arr <int> Array;
Array[1] = 555;
cout << "Is(" << to_string(Array[1]) << ")" << endl;
system("pause");
}
is there any operator like ([]=)? or for this, i have to use which methods? also i want to get value if just used []
The syntax you are attempting for the operator[] functions is quite wrong. You need something like:
// Version for non-const objects
Param& operator[](std::size_t i)
{
return Data[i];
}
// Version for const objects
Param const& operator[](std::size_t i) const
{
return Data[i];
}
If you want to support arrays whose sizes are known at compile time, you can use:
template <typename Param, std::size_t N>
class arr
{
private:
Param Data[N];
public:
Param& operator[](std::size_t i)
{
return Data[i];
}
Param const& operator[](std::size_t i) const
{
return Data[i];
}
};
If you want to support arrays whose sizes are known at run time, you can use the following. However, you need to be aware of The Rule of Three and make sure to implement the copy constructor and the copy assignment operator properly.
template <typename Param>
class arr
{
private:
Param* Data;
public:
arr(size_t size) : Data(new Param[size]) {}
~arr() { delete [] Data; }
Param& operator[](std::size_t i)
{
return Data[i];
}
Param const& operator[](std::size_t i) const
{
return Data[i];
}
};
Foo& operator[] is sufficient for reading and writing.
Include the size of the array as a template parameter, or replace Param[] with std::vector<Param>.
Use size_t instead of int for array index types.
You don't need to wrap Array[1] in to_string for output.
Don't use void main! The standard says main must be int.
void& is not valid C++ either.
#include <iostream>
using namespace std;
template <typename Param, size_t Size> class arr {
private:
Param Data[Size];
public:
Param &operator[](size_t Element_id) {
return Data[Element_id];
}
};
int main() {
arr<int, 3> Array;
Array[1] = 555;
cout << "Is(" << Array[1] << ")" << endl;
}
However, all that arr does in my snippet is be a less useful std::array!
Is it possible to access functions of a class template argument outside the template? I tried the following without success:
class A {
public:
void func () { std::cout << ("A called"); }
};
template<class T>
class tClass {
public:
T* operator->() {
return mem;
}
T* operator*() {
return mem;
}
const T* operator->() const {
return mem;
}
const T* operator*() const {
return mem;
}
private:
T* mem;
};
and in main:
tClass<A>* t = new tClass<A>();
t->func();
I get the following compiler error: error: 'class tClass<A>' has no member named 'func'
Doesn't overriding the -> operator return a pointer to the template argument? I'm asking because I've seen a very similar code that was working. I've also seen other answers suggesting using typedef, but I'm not sure how it applies here.
Ignore the fact that the mem object is not initialized now.
Thanks in advance!
This:
tClass<A>* t = new tClass<A>();
t->func();
isn't calling tClass<A>::operator->, it's dereferencing the tClass<A>* itself. And tClass<A> doesn't have a func() member function, hence the error. You would have to either double dereference:
(*t)->func();
Or use a non-pointer to tClass<A>:
tClass<A> t;
t->func();
Side-note, this phrasing:
return a pointer to the template argument
isn't right. The template argument is a type. In this case, A. You're returning a pointer to something which has that type - not the type itself.
You also need to initialize your pointer T* mem!, e.g.
template<class T>
class tClass {
public:
explicit tClass(T *p):mem(p){}
T* operator->() {
return mem;
}
T* operator*() {
return mem;
}
const T* operator->() const {
return mem;
}
const T* operator*() const {
return mem;
}
private:
T* mem;
};
and then
tClass<A> t(new A() );
and then go on.
I have a template vector class. This is how a part of it looks like:
template<class Type>
class Vector
{
public:
Vector(int size)
{
MyTime = new Type[size];
m_size = size;
m_current = -1;
}
void set(int i,Type &data)
{
data[i]=Mytime[i];
}
private:
Type* MyTime;
int m_size;
int m_current;
};
Then i wish to use the set method above to set the value into a string "records".
for(int i=0 ; i<count ; i++)
{
records.set(i)=dateList.get(i)+timeList3.get(i);
}
But it gives me two errors:
1. too few argument in function call.
2. expression must be a modifiable lvalue.
Both errors appear at the records.set(i).
The declaration of "records" is:
Vector<string> records(100);
This makes me quite confuse. May I know how to solve it?
I think that records.set(i)=dateList.get(i)+timeList3.get(i); is supposed to be this:
records.set(i, dateList.get(i) + timeList3.get(i));
too few argument in function call. - In your example you only pass i, meanwhile there are two arguments required to set
expression must be a modifiable lvalue - set() return void, it's not a method that returns a modifiable lvalue.
Meanwhile, you vector class does some weird things. This is a basic template vector class.
template <typename T>
class Vector
{
public:
Vector(int size) : data(new T[size]) {}
void Set(int index, const T& val) { data[index] = val; }
T Get(int index) { return data[index]; }
private:
T* data;
}
I'm totally new here, so I'm not very familiar with style of writing here, so sorry if the question doesn't look like it should.
My question is, how can I create an array of object, but not with default constructors?
If I have something like this:
set<movie> a(3);
set<movie> b(2);
And constructors:
For movie
movie::movie()
{
this->naziv=0;
this->reditelj=0;
this->trajanje=0;
}
movie::movie(char *name, char *red, int len)
{
this->naziv=new char[strlen(name)+1];
strcpy(naziv,name);
this->reditelj=new char[strlen(red)+1];
strcpy(reditelj,red);
this->trajanje=len;
}
And for set:
template<class t>
set<t>::set()
{
this->br=0;
this->niz=0;
}
set<t>::set(int b)
{
this->br=b;
this->niz=new t[br];
}
Answers are great,but on course they teach us some basic stuff about c++,how to create class,template class,I mean,to write programs from the beginning,so for now we don't use that classes and functions that most of you mentioned. The assignment is to write the code this way,so how can I do that?
The assignment is to make a class and a template class,template class is actually an array of objects,so I should make an object,that's an array of objects,and some other functions.
Here's my whole code:
Set.h
#pragma once
#include<iostream>
using namespace std;
template<class t>
class set
{
int br;
t* niz;
public:
set();
set(int b);
~set();
set(set& copy);
int vrati_br_elem()
{
return br;
}
bool pripada(t elem);
set operator*(set& drugi);
friend istream& operator>> <>(istream& ulaz,set<t> &s);
friend ostream& operator<< <>(ostream& izlaz,set<t> &s);
};
template<class t>
set<t>::set()
{
this->br=0;
this->niz=0;
}
template<class t>
set<t>::set(int b)
{
this->br=b;
this->niz=new t[br];
}
template<class t>
set<t>::~set()
{
if(this->niz!=0)
delete [] niz;
}
template<class t>
bool set<t>::pripada(t elem)
{
for(int i=0;i<this->br;i++)
if(this->niz[i]=elem)
return true;
return false;
}
template<class t>
set<t> set<t>::operator *(set<t> &drugi)
{
int broj=0;
set<t> pom((this->br>drugi.br)?this->br:drugi.br);
for(int i=0;i<this->br;i++)
for(int j=0;j<drugi.br;j++)
if(this->niz[i]==drugi.niz[j])
pom.niz[broj++]=this->niz[i];
pom.br=broj;
return pom;
}
template<class t>
istream& operator>>(istream& ulaz,set<t> &s)
{
for(int i=0;i<s.br;i++)
cin>>s.niz[i];
return ulaz;
}
template<class t>
ostream& operator<<(ostream& izlaz,set<t> &s)
{
for(int i=0;i<s.br;i++)
cout<<endl<<s.niz[i]<<endl;
return izlaz;
}
template<class t>
set<t>::set(set<t> ©)
{
this->br=copy.br;
this->niz=new t[br];
for(int i=0;i<this->br;i++)
this->niz[i]=copy.niz[i];
}
movie.h
#include<iostream>
using namespace std;
class movie
{
char* naziv;
char* reditelj;
int trajanje;
public:
movie();
~movie();
movie(movie& copy);
movie(char* name,char* red,int len);
movie& operator=(movie& film);
bool operator==(movie& film);
friend istream& operator>>(istream& ulaz,movie& film);
friend ostream& operator<<(ostream& izlaz,movie& film);
};
movie.cpp
#include"movie.h"
using namespace std;
movie::movie()
{
this->naziv=0;
this->reditelj=0;
this->trajanje=0;
}
movie::~movie()
{
if(naziv!=0&&reditelj!=0)
{
delete [] naziv;
delete [] reditelj;
}
}
movie::movie(movie ©)
{
this->naziv=new char[strlen(copy.naziv)+1];
strcpy(this->naziv,copy.naziv);
this->reditelj=new char[strlen(copy.reditelj)+1];
strcpy(this->reditelj,copy.reditelj);
this->trajanje=copy.trajanje;
}
movie& movie::operator =(movie &film)
{
if(this!=&film)
{
delete [] naziv;
delete [] reditelj;
this->naziv=new char[strlen(film.naziv)+1];
strcpy(this->naziv,film.naziv);
this->reditelj=new char[strlen(film.reditelj)+1];
strcpy(this->reditelj,film.reditelj);
this->trajanje=film.trajanje;
}
return *this;
}
bool movie::operator ==(movie &film)
{
if(!strcmp(this->naziv,film.naziv)&&!strcmp(this->reditelj,film.reditelj)&&this->trajanje==film.trajanje)
return true;
return false;
}
istream& operator>>(istream& ulaz,movie& film)
{
ulaz>>film.naziv>>film.reditelj>>film.trajanje;
return ulaz;
}
ostream& operator<<(ostream& izlaz,movie& film)
{
izlaz<<endl<<film.naziv<<endl<<film.reditelj<<endl<<film.trajanje<<endl;
return izlaz;
}
movie::movie(char *name, char *red, int len)
{
this->naziv=new char[strlen(name)+1];
strcpy(naziv,name);
this->reditelj=new char[strlen(red)+1];
strcpy(reditelj,red);
this->trajanje=len;
}
Don't use built-in arrays, especially if you are new. Built-in arrays are best left to experts and even then they are often best avoided. Instead of using T[n] just use std::vector<T>. This one will start out empty an you can then e.g. push_back() the objects you are interested in.
That said, I don't see where you code excerpt actually has a problem.
You can create an array of objects invoking the constructor directly.
movie objs[2] = {movie(arg1, arg2, arg3), movie(arg1, arg2, arg3)};
The standard way to do this is to use a allocator object, like all the standard containers.
template<class T, class alloc_type =std::allocator<T> >
class set {
typedef alloc_type::pointer pointer; //bring in it's pointer type
alloc_type alloc;
And then, use that for everything:
pointer buffer = alloc.allocate(100);
alloc.construct(buffer+0); //deault construct T
alloc.construct(buffer+1, T()); //construct T from copy
alloc.construct(buffer+2, 17); //construct T from 17
alloc.destroy(buffer+2); //clean up objects
alloc.destroy(buffer+1);
alloc.destroy(buffer+0);
alloc.deallocate(buffer); //clean up buffer
Remember, it's standard to construct from lowest index to highest, and to destroy in the reverse order.
The "correct" way to do this has changed with C++11, but since I use MSVC10, which can't do the correct way, I still use this way.
Basic implementations of each of these functions is rediculously simple, though.
template<class T>
class myallocator {
public:
typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef const T* const_pointer;
typedef const T& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
myallocator () throw() {}
template <class U> myallocator (const myallocator<U>&) throw() {}
pointer address (reference x) const {return &x;}
const_pointer address (const_reference x) const {return &x;}
size_type max_size() const throw() {return size_type(-1);}
pointer allocate(size_type c,const_pointer h=0){return(T*)new char[sizeof(T)*c];}
void deallocate(pointer ptr, size_type c) {delete [] ptr;}
pointer construct(pointer ptr) {return new(ptr)T;}
template<class U>
pointer construct(pointer ptr, const U& from) {return new(ptr)T(from);}
void destroy(pointer ptr) {ptr->~T();}
};
The two construct members use what is called "placement new" which creates the object in an already existing space. Here, an array of chars.
You are trying to write your own container class, and you should not call it set. I will assume that you are calling it my_set in this answer.
I think this is the line you are interested in. You wish to pass in a default value here:
my_set<t>::my_set(int b, t default_value)
//creates a my_set with 'b' elements, where each element is set to default_value
Is this your goal? If so, it's easier to define niz as a *vector<t> instead of as t*
template<class t>
struct my_set {
int br;
vector<t> *niz;
my_set(int b, const t& default_value);
}
template<class t>
my_set<t>::my_set(int b, const t& default_value) {
//creates a my_set with 'b' elements, where each element is set to 0
this->br=b;
this->niz=new vector<t>(b, default_value);
}
There are other changes you may consider, such as defining niz simply as vector<t>, not as vector<t>*, but I think that's beyond the scope of you original question.
Finally, I have a question of my own for everybody. How can I do an uninitialized array new[] in C++? I'd like to new an array of known size, but with unconstructed data, and then use something like uninitialized_copy to copy data in.
One of the problems in your code is that this will fail if either string is 0
ostream& operator<<(ostream& izlaz,movie& film)
{
izlaz
<< endl << film.naziv // fails if film.naziv == 0
<< endl << film.reditelj // fails if film.reditelj == 0
<< endl << film.trajanje << endl;
return izlaz;
}
That crashes for me. You should not do cout << (char*)0;. It's better to do something like cout << "". You could fix it by changing the constructor of movie:
movie::movie()
{
this->naziv=""; // an empty, non-null, string
this->reditelj=""; // an empty, non-null, string
this->trajanje=0;
}
But a better solution is to stop using char * and use std :: string instead.
The expression new T[n] will always allocate space for n T objects and call the T constructor on each element. Similarly, delete[] niz, will always call the T destructor on each element. It seems that you want to manually control when the T constructor and destructor are called, so rather than using ::operator new[], you could just use ::operator new and its placement syntax.
You want niz to be an array of br T objects. Instead of this:
niz = new T[br];
You can use this:
niz = static_cast<T *>(::operator new(br * (sizeof (T))));
which will allocate space in the heap for br contiguous T objects, but not call the T constructor on any of them. It's basically like using malloc() to allocate space for the T objects.
But, now you have a problem: how do you actually use one of the T objects? Before you can do anything with niz[i], you need to make sure that the ith T object has been constructed. You can use placement new to construct it:
new(niz + i) T();
Notice that niz + i is the pointer to the ith T object. The effect of this statement is that the T constructor is called in place using the space at reinterpret_cast<char *>(niz + i) through reinterpret_cast<char *>(niz + i) + (sizeof (T)).
Now you have another problem: how do you keep track of which T objects have been constructed? You need to know this in order to call the destructor on the ones that have been constructed, or else you might leak memory.
One solution is to use a std::vector<bool> having br bool objects. If the ith bool is true, then you will know that the ith T object was constructed. Otherwise, it needs to be constructed.
In the set<T> destructor, you need to make sure to destroy all T objects that have been constructed. Suppose that the ith T object has been constructed. To call the T destructor on the ith T object, you can use this statement:
(niz + i)->~T();
Putting it all together, you would get something like this:
#include <cstddef>
#include <iostream>
#include <new>
#include <vector>
template <typename T>
class set
{
std::size_t br;
T *niz;
std::vector<bool> constructed;
public:
std::string name;
set()
: br(0), niz(NULL), constructed()
{
}
set(std::size_t br)
: br(br), niz(NULL), constructed(br, false)
{
niz = static_cast<T *>(::operator new(br * (sizeof (T))));
}
void destroy()
{
std::cout << "~set(" << name << ")\n";
if (niz) {
std::vector<bool>::const_iterator begin = constructed.begin(), it, end = constructed.end();
for (it = constructed.begin(); it != end; ++it) {
if (*it) {
(niz + (it - begin))->~T();
}
}
::operator delete(niz);
}
}
~set()
{
destroy();
}
set<T>& operator=(const set<T>& other)
{
if (this != &other) {
destroy();
niz = NULL;
constructed = std::vector<bool>(other.br, false);
br = other.br;
T *tmp = static_cast<T *>(::operator new(other.br * (sizeof (T))));
try {
std::size_t i;
for (i = 0; i < other.br; ++i) {
if (other.constructed[i]) {
new(tmp + i) T(other.niz[i]);
constructed[i] = true;
}
}
} catch (...) {
niz = tmp;
destroy();
throw;
}
niz = tmp;
name = other.name + " (2)";
}
return *this;
}
T& operator[](std::size_t i)
{
if (niz && !constructed[i]) {
new(niz + i) T();
constructed[i] = true;
}
return niz[i];
}
};
struct my_struct
{
char c;
my_struct(char c = 'a')
: c(c)
{
std::cout << "my_struct('" << c << "')\n";
}
~my_struct()
{
std::cout << "~my_struct('" << c << "')\n";
}
};
int main()
{
::set<char> a, a2(3);
a.name = "a";
a2.name = "a2";
{
::set<my_struct> b(3);
b.name = "b";
b[0].c = '1';
b[2].c = '3';
b[1].c = '2';
::set<my_struct> b2(4);
b2.name = "b2";
b = b2; b.name += ", going by the name 'b'";
b[0].c = 'A';
b2[1].c = 'B';
}
}
Note: I do not recommend actually using this code. The point is to learn about the placement new operator and explicitly invoking a destructor through a pointer.
See STL templates vector and set for standard alternatives.
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?!...
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.
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).
#include <cassert>
struct X {int a, b; };
template <typename T>
struct Initialized
{
T t;
// default (initialized) ctor
Initialized(): t() { }
};
template <typename T>
struct WithInheritance: public T
{
// default (initialized) ctor
WithInheritance(): T() { }
};
int main()
{
Initialized<X> a;
assert(a.t.a == 0 && a.t.b == 0);
//it would probably be more reasonable not to support arrays,
//and use boost::array / std::tr1::array instead
Initialized<int[2]> b;
assert(b.t[0] == 0 && b.t[1] == 0);
WithInheritance<X> c;
assert(c.a == 0 && c.b == 0);
}
In your quest to determine the pod-ness of a type, you might also take into account this note from boost::is_pod reference:
Without some (as yet unspecified) help
from the compiler, is_pod will never
report that a class or struct is a
POD; this is always safe, if possibly
sub-optimal. Currently (May 2005) only
MWCW 9 and Visual C++ 8 have the
necessary compiler intrinsics.
(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_pod that actually works.)
But if you want to specialize based on a condition, you can introduce a bool parameter. E.g something like this:
#include <limits>
#include <cstdio>
template <class T, bool b>
struct SignedUnsignedAux
{
void foo() const { puts("unsigned"); }
};
template <class T>
struct SignedUnsignedAux<T, true>
{
void foo() const { puts("signed"); }
};
//using a more reliable condition for an example
template <class T>
struct SignedUnsigned: SignedUnsignedAux<T, std::numeric_limits<T>::is_signed > {};
int main()
{
SignedUnsigned<int> i;
SignedUnsigned<unsigned char> uc;
i.foo();
uc.foo();
}
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.
#include <boost/type_traits.hpp>
#include <iostream>
template <class T, bool B = boost::is_scalar<T>::value>
struct Initialized
{
T value;
Initialized(const T& value = T()): value(value) {}
operator T& () { return value; }
operator const T& () const { return value; }
};
template <class T>
struct Initialized<T, false>: public T
{
Initialized(const T& value = T()): T(value) {}
};
template <class T, size_t N>
struct Initialized<T[N], false>
{
T array[N];
Initialized(): array() {}
operator T* () { return array; }
operator const T* () const { return array; }
};
//some code to exercise it
struct X
{
void foo() const { std::cout << "X::foo()" << '\n'; }
};
void print_array(const int* p, size_t size)
{
for (size_t i = 0; i != size; ++i) {
std::cout << p[i] << ' ';
}
std::cout << '\n';
}
template <class T>
void add_one(T* p, size_t size)
{
for (size_t i = 0; i != size; ++i) {
p[i] += T(1);
}
}
int main()
{
Initialized<int> a, b = 10;
a = b + 20;
std::cout << a << '\n';
Initialized<X> x;
x.foo();
Initialized<int[10]> arr /*= {1, 2, 3, 4, 5}*/; //but array initializer will be unavailable
arr[6] = 42;
add_one<int>(arr, 10); //but template type deduction fails
print_array(arr, 10);
}
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.
I know it doesn't answer your question, but I thought POD structures were always zero-initialised anyway.
Since I was able to use UncleBen's answers to create a comprehensive solution (as good as I think it gets at this point in C++), I wanted to share it, below:
Feel free to use it, but I make no guarantees whatsoever about its worthiness for any use whatsoever, etc., etc., be an adult and take responsibility for your own damn actions, blah, blah:
//////////////////////////////////////////////////////////////
// Raw Memory Initialization Helpers
//
// Provides:
// Zero(x) where x is any type, and is completely overwritten by null bytes (0).
// Initialized<T> x; where T is any legacy type, and it is completely null'd before use.
//
// History:
//
// User UncleBen of stackoverflow.com and I tried to come up with
// an improved, integrated approach to Initialized<>
// http://stackoverflow.com/questions/2238197/how-do-i-specialize-a-templated-class-for-data-type-classification
//
// In the end, there are simply some limitations to using this
// approach, which makes it... limited.
//
// For the time being, I have integrated them as best I can
// However, it is best to simply use this feature
// for legacy structs and not much else.
//
// So I recommend stacked based usage for legacy structs in particular:
// Initialized<BITMAP> bm;
//
// And perhaps some very limited use legacy arrays:
// Initialized<TCHAR[MAX_PATH]> filename;
//
// But I would discourage their use for member variables:
// Initialized<size_t> m_cbLength;
// ...as this can defeat template type deduction for such types
// (its not a size_t, but an Initialized<size_t> - different types!)
//
//////////////////////////////////////////////////////////////
#pragma once
// boost
#include <boost/static_assert.hpp>
#include <boost/type_traits.hpp>
// zero the memory space of a given PODS or native array
template <typename T>
void Zero(T & object, int zero_value = 0)
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
// make zeroing out a raw pointer illegal
BOOST_STATIC_ASSERT(!(boost::is_pointer<T>::value));
::memset(&object, zero_value, sizeof(object));
}
// version for simple arrays
template <typename T, size_t N>
void Zero(T (&object)[N], int zero_value = 0)
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
::memset(&object, zero_value, sizeof(object));
}
// version for dynamically allocated memory
template <typename T>
void Zero(T * object, size_t size, int zero_value = 0)
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
::memset(object, zero_value, size);
}
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Initialized for non-inheritable types
// usage: Initialized<int> i;
template <typename T, bool SCALAR = boost::is_scalar<T>::value>
struct Initialized
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_scalar<T>::value));
// the data
T m_value;
// default valued construction
Initialized() : m_value() { }
// implicit valued construction (auto-conversion)
template <typename U> Initialized(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; }
// zero method for this type
void _zero() { m_value = T(); }
};
//////////////////////////////////////////////////////////////////////////
// Initialized for inheritable types (e.g. structs)
// usage: Initialized<RECT> r;
template <typename T>
struct Initialized<T, false> : public T
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
// default ctor
Initialized() : T() { }
// auto-conversion ctor
template <typename OtherType> Initialized(const OtherType & value) : T(value) { }
// auto-conversion assignment
template <typename OtherType> Initialized & operator = (const OtherType & value) { *this = value; }
// zero method for this type
void _zero() { Zero((T&)(*this)); }
};
//////////////////////////////////////////////////////////////////////////
// Initialized arrays of simple types
// usage: Initialized<char, MAXFILENAME> szFilename;
template <typename T, size_t N>
struct Initialized<T[N],false>
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
// internal data
T m_array[N];
// default ctor
//Initialized() : m_array() { } // Generates a warning about new behavior. Its okay, but might as well not produce a warning.
Initialized() { Zero(m_array); }
// array access
operator T * () { return m_array; }
operator const T * () const { return m_array; }
// NOTE: All of the following techniques leads to ambiguity.
// Sadly, allowing the type to convert to ArrayType&, which IMO should
// make it fully "the same as it was without this wrapper" instead causes
// massive confusion for the compiler (it doesn't understand IA + offset, IA[offset], etc.)
// So in the end, the only thing that truly gives the most bang for the buck is T * conversion.
// This means that we cannot really use this for <char> very well, but that's a fairly small loss
// (there are lots of ways of handling character strings already)
// // automatic conversions
// operator ArrayType& () { return m_array; }
// operator const ArrayType& () const { return m_array; }
//
// T * operator + (long offset) { return m_array + offset; }
// const T * operator + (long offset) const { return m_array + offset; }
//
// T & operator [] (long offset) { return m_array[offset]; }
// const T & operator [] (long offset) const { return m_array[offset]; }
// metadata
size_t GetCapacity() const { return N; }
// zero method for this type
void _zero() { Zero(m_array); }
};
//////////////////////////////////////////////////////////////////////////
// Initialized for pointers to simple types
// usage: Initialized<char*> p;
// Please use a real smart pointer (such as std::auto_ptr or boost::shared_ptr)
// instead of this template whenever possible. This is really a stop-gap for legacy
// code, not a comprehensive solution.
template <typename T>
struct Initialized<T*, true>
{
// the pointer
T * m_pointer;
// default valued construction
Initialized() : m_pointer(NULL) { }
// valued construction (auto-conversion)
template <typename U> Initialized(const U * rhs) : m_pointer(rhs) { }
// assignment
template <typename U> T * & operator = (U * rhs) { if (m_pointer != rhs) m_pointer = rhs; return *this; }
template <typename U> T * & operator = (const U * rhs) { if (m_pointer != rhs) m_pointer = rhs; return *this; }
// implicit conversion to underlying type
operator T * & () { return m_pointer; }
operator const T * & () const { return m_pointer; }
// pointer semantics
const T * operator -> () const { return m_pointer; }
T * operator -> () { return m_pointer; }
const T & operator * () const { return *m_pointer; }
T & operator * () { return *m_pointer; }
// allow null assignment
private:
class Dummy {};
public:
// amazingly, this appears to work. The compiler finds that Initialized<T*> p = NULL to match the following definition
T * & operator = (Dummy * value) { m_pointer = NULL; ASSERT(value == NULL); return *this; }
// zero method for this type
void _zero() { m_pointer = NULL; }
};
//////////////////////////////////////////////////////////////////////////
// Uninitialized<T> requires that you explicitly initialize it when you delcare it (or in the owner object's ctor)
// it has no default ctor - so you *must* supply an initial value.
template <typename T>
struct Uninitialized
{
// valued initialization
Uninitialized(T initial_value) : m_value(initial_value) { }
// valued initialization from convertible types
template <typename U> Uninitialized(const U & initial_value) : m_value(initial_value) { }
// assignment
template <typename U> T & operator = (const U & rhs) { if (&m_value != &rhs) m_value = rhs; return *this; }
// implicit conversion to underlying type
operator T & () { return m_value; }
operator const T & () const { return m_value; }
// the data
T m_value;
};
//////////////////////////////////////////////////////////////////////////
// Zero() overload for Initialized<>
//////////////////////////////////////////////////////////////////////////
// version for Initialized<T>
template <typename T, bool B>
void Zero(Initialized<T,B> & object)
{
object._zero();
}