Jump to content

  • Log In with Google      Sign In   
  • Create Account





Subverting the C++ type system - Bad Style?

Posted by mrbastard, 15 February 2007 · 129 views

I've been using this template iterator for a while now, and find it really useful.

(I suggest skipping to the tests to get an idea of what it looks like in practice)

The inspiration was to help avoid rewriting code that operates on vertices again and again for different game engines / libs with different vertex types. It works nicely on pixels too and allows me to write generic image processing functions in an STL-ish style.

The first use was to rewrite some basic heightmapping stuff as a template function that operated on iterators conforming to STL's RandAccessIterator concept. I then used the below iterator to allow me to generate vertices directly into (almost) anywhere I want. It works well for streaming directly into a mapped VBO for instance, saving creation and copying of a system memory copy. It also means I'll never have to write it again - it will work with any Vertex, Vec3, plain array[] whatever structure. I've used it with my own engine and Irrlicht so far.

It may seem familiar to anyone who has used the gl*Pointer family of functions.

Thing is, it deliberately subverts the C++ type system.

Bad style?


The iterator itself:
//InterleavedArrayIterator.h
//Provides iterator classes for accesing arbitrary arrays of interleaved components
//Primary uses are to allow STL-like generalised algorithms to iterate over any data
//that can be used as OpenGL vertex attribute data, and to allow image processing algorithms to be written
//without relying on a specific format/layout in memory .
//That being the case, it will operate on any sequential block of memory (e.g. plain arrays, std::vector)
//Given a compound class it provides an interface to iterate over some or all of the components as
//if they were a plain array of the component type. The pointer to member operator is also implemented,
//so the dereferenceable component type can itself be a compound type.

//see the tests for examples

#include <iterator>

//Models the stl concept of Random Access Iterator
//dereference is not range checked, for speed

template<typename dereftype>
class InterleavedArrayIterator : public std::iterator<std::random_access_iterator_tag,dereftype>
//note: implicit distance type is platform int, at least on ms compiler
//ms stl and alloc stuff assumes that if you can alloc it, you can index it with an int. Fair enough.
//I use distance_type instead of int, so hopefully the compiler and stl implementation should always sort it
//Still, it's worth checking the platform distance_type before using on 64bit platforms where mem can be > 2GB
{
distance_type m_stridebytes; //full element size in bytes
distance_type m_size; //number of adjacent dereferenceable elements
const void * m_pointer;

distance_type full_pos;
distance_type derefable_pos;

public:
//copy constructor
InterleavedArrayIterator(const InterleavedArrayIterator<dereftype>& r)
{
//copy vals
m_size = r.m_size;
m_stridebytes = r.m_stridebytes;
m_pointer = r.m_pointer;
full_pos = r.full_pos;
derefable_pos=r.derefable_pos;
}
//same params as gl*Pointer
InterleavedArrayIterator(const unsigned int& size, const unsigned int& stride, const void* ptr)
{
m_size =size; //change to use param list for speed
m_stridebytes = stride;
m_pointer = ptr;
full_pos=0;
derefable_pos=0;
}

///assignment operator
InterleavedArrayIterator<dereftype>&
operator=(const InterleavedArrayIterator<dereftype>& r)
{
if(this == &r)
{
return *this;
}
//copy vals
m_size = r.m_size;
m_stridebytes = r.m_stridebytes;
m_pointer = r.m_pointer;
full_pos = r.full_pos;
derefable_pos=r.derefable_pos;
return *this;
}
///preincrement operator; to model ForwardIterator; returns incremented value
const InterleavedArrayIterator<dereftype>&
operator++()
{
derefable_pos++;
if(derefable_pos >= m_size)
{
derefable_pos -=m_size;
full_pos++;
}
return *this;
}
///postincrement operator; to model ForwardIterator, returns the value before increment
const InterleavedArrayIterator<dereftype>
operator++(int a)
{
InterleavedArrayIterator<dereftype>before(*this);
++(*this);
return before;
}

///predecrement operator; to model Bidirectional Iterator; returns incremented value
const InterleavedArrayIterator<dereftype>&
operator--()
{
derefable_pos--;
if(derefable_pos < 0)
{
derefable_pos += m_size;
full_pos--;
}
return *this;
}

///postdecrement operator; to model Bidirectional Iterator; returns the value before increment
const InterleavedArrayIterator<dereftype>
operator--(int a)
{
InterleavedArrayIterator<dereftype>before(*this);
--(*this);
return before;
}

///iterator addition & ; to model RandomAccessIterator
InterleavedArrayIterator<dereftype>&
operator+=(const distance_type& right)
{
distance_type m = right%m_size;
derefable_pos += m;
full_pos += (right-m)/m_size;
if(derefable_pos >= m_size)
{
full_pos++;
derefable_pos -= m_size;
}
return *this;
}

///iterator addition; to model RandomAccessIterator
//make this friend func? makes more sense
const InterleavedArrayIterator<dereftype>
operator+ (const distance_type& right) const
{
InterleavedArrayIterator<dereftype>r(*this);
r+=right;
return r;
}

///iterator subtraction & ; to model RandomAccessIterator
InterleavedArrayIterator<dereftype>&
operator-=(const distance_type& right)
{
distance_type m = right%m_size;
derefable_pos -= m;
full_pos -= (right-m)/m_size;
if(derefable_pos < 0)
{
full_pos--;
derefable_pos += m_size;
}
return *this;
}

///iterator subtraction ; to model RandomAccessIterator
const InterleavedArrayIterator<dereftype>
operator- (const distance_type& right) const
{
InterleavedArrayIterator<dereftype>r(*this);
r-=right;
return r;
}

///difference ; to model RandomAccessIterator
const difference_type operator-(const InterleavedArrayIterator<dereftype>& right) const
{
return ((full_pos - right.full_pos)*m_size) + (derefable_pos - right.derefable_pos);
}

///element access operator ; to model RandomAccessIterator
dereftype& operator[](const distance_type& x) const
{
InterleavedArrayIterator<dereftype>r(*this);
r+=x;
return * r;
}

///operator lessthan; to model LessThan Comparable for RandomAccessIterator
bool operator<(const InterleavedArrayIterator<dereftype>&right)const
{
return ((full_pos*m_size)+derefable_pos)
< ( (right.full_pos*right.m_size)+right.derefable_pos);
}

///dereference ; to model TrivialIterator etc
dereftype& operator*() const
{
unsigned char * ptr = (unsigned char*)m_pointer + (full_pos*m_stridebytes) + (derefable_pos*sizeof(dereftype));
return *(dereftype*)ptr;
}

///
dereftype* operator->() const
{
unsigned char * ptr = (unsigned char*)m_pointer + (full_pos*m_stridebytes) + (derefable_pos*sizeof(dereftype));
return (dereftype*)ptr;
}

///comparison ; to model Equality Comparable
bool operator==(const InterleavedArrayIterator<dereftype>&right)const
{
if(right.derefable_pos != derefable_pos)
{
return false;
}
if(right.full_pos != full_pos)
{
return false;
}
if(right.m_pointer != m_pointer)
{
return false;
}
if(right.m_stridebytes != m_stridebytes)
{
return false;
}
if(right.m_size != m_size)
{
return false;
}
return true;

}
///comparison ; to model Equality Comparable
bool operator!=(const InterleavedArrayIterator<dereftype>&right)
{
return ! (*this == right);
}
};








a handy proxy for safely(?) creating IAIs
#ifndef INTERLEAVEDARRAYPROXY_H
#define INTERLEAVEDARRAYPROXY_H

#include "InterleavedArrayIterator.h"

//!A Proxy for creating InterleavedArrayIterators from specific containers/arrays
template <typename dereftype>
class InterleavedArrayProxy
{
typedef InterleavedArrayIterator<dereftype> iterator;

//!full element size in bytes
unsigned int m_stridebytes;
//!number of adjacent dereferenceable elements
unsigned int m_size;
//!base pointer
const void * m_pointer;
//!total number of elements
/*!note: not full_elements but number of m_size clusters*/
unsigned int m_numelements;

public:
InterleavedArrayProxy(const unsigned int& size, const unsigned int& stride, const void* ptr, const unsigned int& numelements)
{
m_size = size;
m_stridebytes = stride;
m_pointer = ptr;
m_numelements = numelements;
}
InterleavedArrayIterator<dereftype> begin() const
{
return iterator(m_size,m_stridebytes,m_pointer);
}

InterleavedArrayIterator<dereftype> end() const
{
iterator i(begin());
i+=(m_numelements*m_size);
return i;
}
};

#endif //defined INTERLEAVEDARRAYPROXY_H







some boost unit tests for completeness sake
// InterleavedArrayIteratorTestMain.cpp : Defines the entry point for the console application.
//

#define BOOST_AUTO_TEST_MAIN
#include <boost/test/auto_unit_test.hpp>

#include "InterleavedArrayProxy.h"

#include <algorithm>
#include<numeric>

struct compound
{
float x,y;
};

struct testvert
{
float x,y,z;
float nx,ny,nz;
compound tex_coords;
};



//should really just assign the verts once as a test fixture - requires less automatic use of boost.test

BOOST_AUTO_UNIT_TEST( filltest )
{
/*
tests the following:

IAP:
constructor
begin
end
IAI:
copy constructor
constructor
preincrement
iterator addition
dereference*
comparison

*/

testvert v[100];
InterleavedArrayProxy<float>proxy(3,sizeof(testvert),v,100);
std::fill(proxy.begin(),proxy.end(),1.0f);
bool failed = false;
for(unsigned int count=0; count<100; count++)
{
if ( (v[count].x != 1.0) || (v[count].y != 1.0) || (v[count].z != 1.0) )
{
failed = true;
}
}
BOOST_CHECK(!failed);

}

BOOST_AUTO_UNIT_TEST(testSTLfor_each)
{

}

/*
The following are assumed to be trivial and are not tested:
operator==
operator!=
construction
destruction

*/


BOOST_AUTO_UNIT_TEST(testPointerDereferenceOperator)
{
testvert v[100];
InterleavedArrayProxy<compound>proxy(1,sizeof(testvert),&v[0].tex_coords,100);
InterleavedArrayIterator<compound>i = proxy.begin();
unsigned int t = 0;
for(;i!=proxy.end();++i)
{
i->x=(float)t;
i->y=(float)t;
t++;
}
bool failed = false;
t=0;
for (i=proxy.begin();i!=proxy.end();++i)
{
if ((i->x)!=(float)t || (i->y)!=(float)t)
{
failed = true;
}
t++;
}
BOOST_CHECK(!failed);
}

BOOST_AUTO_UNIT_TEST(testElementAccess)
{
testvert v[100];
InterleavedArrayProxy<float>proxy(3,sizeof(testvert),v,100);
InterleavedArrayIterator<float>i = proxy.begin();
unsigned int t = 0;
for(;i!=proxy.end();++i)
{
*i=(float)t;
t++;
}
bool failed = false;
i=proxy.begin();
for (unsigned int count=0;count<300;count++)
{
if (i[count]!=count)
{
failed = true;
}
i[count]=666.0f;
}
BOOST_CHECK(!failed);

for (unsigned int count=0;count<300;count++)
{
if (i[count]!=666.0f)
{
failed = true;
}
}
BOOST_CHECK(!failed);
}

BOOST_AUTO_UNIT_TEST (testAssignment)
{
//assumes above tests have been passed
InterleavedArrayProxy<float>proxy(3,sizeof(testvert),NULL,100);
InterleavedArrayIterator<float> i = proxy.begin();
BOOST_CHECK(i == proxy.begin());

}

BOOST_AUTO_UNIT_TEST(testPlusEqualsDistanceType)
{
//assumes operator++ works
InterleavedArrayProxy<float>proxy(3,sizeof(testvert),NULL,100);
InterleavedArrayIterator<float> i = proxy.begin();
InterleavedArrayIterator<float> j = proxy.begin();
i+=1;
j++;
BOOST_CHECK(i==j);
}

BOOST_AUTO_UNIT_TEST(testMinusEqualsDistanceType)
{
//assumes operator-- works
InterleavedArrayProxy<float>proxy(3,sizeof(testvert),NULL,100);
InterleavedArrayIterator<float> i = proxy.begin();
InterleavedArrayIterator<float> j = proxy.begin();
i-=1;
j--;
BOOST_CHECK(i==j);
}

BOOST_AUTO_UNIT_TEST(testPostIncrement)
{
//assumes above tests have been passed
//assumes operator+(distance_type) works
InterleavedArrayProxy<float>proxy(3,sizeof(testvert),NULL,100);
InterleavedArrayIterator<float> i = proxy.begin();
BOOST_CHECK(i++ == proxy.begin()); //check the value stays the same inside the statement
BOOST_CHECK(i == proxy.begin()+1); //check the value has been incremented really

}

BOOST_AUTO_UNIT_TEST(testPreDecrement)
{
//test assumes IAP works
//test assumes IAI::operator==(IAI), IAI::operator= and IAI::preinc are working
InterleavedArrayProxy<float>proxy(3,sizeof(testvert),NULL,100);
InterleavedArrayIterator<float> i = proxy.begin();
++i;
--i;
BOOST_CHECK(i == proxy.begin()); //check the value has been incremented really
}

BOOST_AUTO_UNIT_TEST(testSubtractDistanceType)
{
//test assumes IAP works
//test assumes IAI::operator==(IAI), IAI::operator= and IAI::preinc are working
//internally assumes operator-=(distance_type) works
testvert v[100];
InterleavedArrayProxy<float>proxy(3,sizeof(testvert),v,100);
InterleavedArrayIterator<float> a = proxy.begin();
++a;
BOOST_CHECK(proxy.begin() == (a-1));

}

BOOST_AUTO_UNIT_TEST(testPostDecrement)
{
//test assumes IAP works,
//test assumes IAI::operator=, IAI::operator== and IAI::operator-(distance_type) work
//internally assumes predecrement works
InterleavedArrayProxy<float>proxy(3,sizeof(testvert),NULL,100);
InterleavedArrayIterator<float> i = proxy.begin();
BOOST_CHECK(i-- == proxy.begin()); //check the value stays the same inside the statement
BOOST_CHECK(i == proxy.begin()-1); //check the value has been decremented really
}

BOOST_AUTO_UNIT_TEST(testDifference)
{
//test assumes IAP works,
//test assumes IAI::operator=, IAI::operator== work
InterleavedArrayProxy<float>proxy(3,sizeof(testvert),NULL,100);
InterleavedArrayIterator<float> i = proxy.begin();
++i;
int difference = (int)(i - proxy.begin());
BOOST_CHECK(difference==1);
difference = (int)(proxy.begin() -i);
BOOST_CHECK(difference==-1);
}

// _____________________________________











December 2014 »

S M T W T F S
 123456
78910111213
14151617181920
212223242526 27
28293031   

Recent Entries

Recent Entries

PARTNERS