Re: Maintance of c++ code



In article <du9g8c$bf$1@xxxxxxxxxxxxxxxxxxxxxxxxxx>,
piotr5@xxxxxxxxxxxxxxxxx (Piotr Sawuk) wrote:

In article <postmaster-D6D607.10545701032006@xxxxxxxxxxxxxxxxxxxxxxx>,
"Daniel T." <postmaster@xxxxxxxxxxxxx> writes:
In article <dttvmf$j0$1@xxxxxxxxxxxxxxxxxxxxxxxxxx>,
piotr5@xxxxxxxxxxxxxxxxx (Piotr Sawuk) wrote:

In article <postmaster-84682A.14091723022006@xxxxxxxxxxxxxxxxxxxxxxx>,
"Daniel T." <postmaster@xxxxxxxxxxxxx> writes:
In article <dt91dn$me4$1@xxxxxxxxxxxxxxxxxxxxxxxxxx>,
piotr5@xxxxxxxxxxxxxxxxx (Piotr Sawuk) wrote:


You somehow keep forgetting that not OOP with virtual functions,
but OOP with templates is a whole new world for me...

That's a problem, because templates are not a method of implementing
OOP. They are not a means of doing different things with the same
(abstract) type, they are a means of doing the same thing with different
(concrete) types. Templates implement generic programming not
object-oriented programming.

Actually OOP does more: it allows doing different things with the same
data-structure, provided the VTable is stored together with this structure.
This allows for storing an object and retrieving it lateron for this
OOP-stuff. Templates truely can't do that! My opinion isn't that far
from yours, I just emphasize efficiency. Maybe the compiler is able to
optimize for early-binding, but I don't think any c++-compiler would
be able to optimize out the VTable and pass objects in the registers
of the underlying assembly-language when "pass by reference" was used
in the function-definition. Only higher-level languages can do that!

Using templates is as good as true abstract types, it's just
lacking in the late-binding department (there is none). But as
my experience told me, late-binding is actually rarely needed
(i.e. many abstract objects and the functions using them could
be re-written so that only early-binding is used and no additional
member for type-identification is needed). It's just that the use
of templates is a design-decision which can't be un-done lateron
that easily, and which is somehow incompatible to the design-decision
of using virtual members (because typenames are not "virtual", and
sending the base-class will always slice-off all typedefs which
are important for template-stuff). In the same way as defining a
function needs to take care to take parameters only by reference,
(since otherwise those parameter's virtual members wont be used
correctly), in the same way defining a templated function needs
to take care to create an individual template-parameter for each
parameter of the function. For example as I learned stl-algorithms
are a bad design for OOP through templates because iterator-ranges
are expected to consist of 2 equal types (instead of making one
iterator constant in the standard, such that end-conditions could
be handled individually depending on each iterator's type). And
at the same time stl-algorithms are a bad design for real OOP since
iterators are taken as a pair of equal type instead of taking one
of them by reference. just try to use some stl-algorithm on:

template <class Iterator_>
class double_iterator : public Iterator_
{
public:
typedef Iterator_ iterator_type;
typedef double_iterator<Iterator_> Self_;

double_iterator() : iterator_type() {}
explicit double_iterator(iterator_type i) : iterator_type(i) {}

double_iterator(const Self_& i) :
iterator_type(static_cast<iterator_type>(i)) {}
template <class Iter_>
double_iterator(const double_iterator<Iter_>& i)
: iterator_type(static_cast<iterator_type>(i)) {}

Self_& operator++() {
iterator_type::operator++();
iterator_type::operator++();
return *this;
}
Self_ operator++(int) {
Self_ tmp = *this;
iterator_type::operator++();
iterator_type::operator++();
return tmp;
}
Self_& operator--() {
iterator_type::operator--();
iterator_type::operator--();
return *this;
}
Self_ operator--(int) {
Self_ tmp = *this;
iterator_type::operator--();
iterator_type::operator--();
return tmp;
}
};

//TODO:I don't think double_iterator<double_iterator> would work as expected!
//Warning: "==" and "!=" are asymetric: the second argument is turned into
// a range [j,j+2) in which first argument is searched for...
template <class Iter_>
bool operator==(const double_iterator<Iter_> i,
const Iter_ j)
{
Iter_ tmp=static_cast<Iter_>(i);
return tmp == j || --tmp == j;
}
template <class Iter_>
bool operator==(const Iter_ j,
const double_iterator<Iter_> i)
{
Iter_ tmp=static_cast<Iter_>(i);
return tmp == j || ++tmp == j;
}
template <class Iter_>
bool operator!=(const double_iterator<Iter_> i,
const Iter_ j)
{return !(i == j);}
template <class Iter_>
bool operator!=(const Iter_ j,
const double_iterator<Iter_> i)
{return !(j == i);}

By definition, the above class is not an iterator, op== must be
symmetric for the class to fit the definition of an iterator.

The idea is generally useful though, so lets see what we can do to make
it work:

First write the tests:

int main()
{
{
int data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
std::vector<int> in( data, data + 10 );
std::vector<int> out;
std::copy( double_iterator( in.begin() ),
double_iterator( in.end() ), std::back_inserter( out ) );
assert( out.size() == 5 );
assert( out[0] == 0 );
assert( out[1] == 2 );
assert( out[2] == 4 );
assert( out[4] == 8 );
}
{
int data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
std::vector<int> in( data, data + 9 );
std::vector<int> out;
std::copy( double_iterator( in.begin() ),
double_iterator( in.end() ), std::back_inserter( out ) );
assert( out.size() == 5 );
assert( out[0] == 0 );
assert( out[1] == 2 );
assert( out[2] == 4 );
assert( out[4] == 8 );
}
std::cout << "working!\n";
}

Then write the code to make it work:

template < typename Iterator >
class double_iterator_t : public std::iterator<std::forward_iterator_tag,
typename std::iterator_traits<Iterator>::value_type,
typename std::iterator_traits<Iterator>::difference_type,
typename std::iterator_traits<Iterator>::pointer,
typename std::iterator_traits<Iterator>::reference>
{
Iterator prev;
Iterator rep;
public:
explicit double_iterator_t( Iterator i ): prev(), rep( i ) { }

typename std::iterator_traits<Iterator>::value_type operator*() const
{
return *rep;
}

typename std::iterator_traits<Iterator>::value_type& operator*() {
return *rep;
}

double_iterator_t& operator++() {
++rep;
prev = rep;
++rep;
return *this;
}

double_iterator_t operator++(int) {
double_iterator_t tmp( *this );
++rep;
prev = rep;
++rep;
return tmp;
}

friend bool operator==( double_iterator_t<Iterator> lhs,
double_iterator_t<Iterator> rhs ) {
return lhs.rep == rhs.prev || lhs.rep == rhs.rep ||
lhs.prev == rhs.rep;
}
};

template < typename Iterator >
bool operator!=( double_iterator_t<Iterator> lhs,
double_iterator_t<Iterator> rhs ) {
return ! ( lhs == rhs );
}

template < typename Iterator >
double_iterator_t<Iterator> double_iterator( Iterator iter ) {
return double_iterator_t<Iterator>( iter );
}

(BTW: as written, a double_iterator_t of a double_iterator_t will work
fine. It would end up being a quadruple_iterator...)

As an exorcise, make the double_iterator_t into a random access iterator
type...


Anyway, each container-type has a different storage-policy, and
there are many more possibilities than those few defined in stl.
In my opinion it is impossible to create code which is useful
for all cases. That's just a warning. Now I'll ty to forget this
prejudice and consider your proposal.

Your quite right. Some algorithms in the standard library work with any
container, some only work with particular containers. What's your point?

My point are user-defined containers! As I said below, for example
the map-container does store my objects in key-value pairs, and
a container created by some other programmer might create some
different means of accessing the values of his container. It's
true that I'm stressing "code-reuse" a little bit too much, but
I for example would like to use stl's find-algorithm together
with above double_iterator class without the need of some means
for tracking whether the container stores an even or an odd number
of elements, or if the container's iterator has been increased
an even or an odd amount of times...

Your problem is that you insist on inheriting from classes that
shouldn't be inherited from. Notice that my class does what you want
without having to track whether the container stores an even or an odd
number of elements or if the container's iterator has been increased an
even or an odd number of times...

My class works because it *doesn't* inherit from something it merely
uses.


I'm currently working on a group of template classes that implement the
active object pattern and will work with many different and otherwise
completely unrelated classes. The idea is that by wrapping an object of
some class with my "ActiveObject" all member-functions will return
immediately rather than block until complete.

Interesting. And how does your class access the names of the classes'
member-functions? Through template-parameters? Or do you abuse the
VTable (i.e. override the usual dispatching)? Or do you merely apply
that behaviour on the classes' standard operators?

The client passes a functor that will be used on the contained object.
For example:

class Foo {
public:
void bar();
};

int main() {
Seperate< Foo > foo( new Foo );
foo.call( mem_fun( &Foo::bar ) );
}

IE I use a template parameter, and don't abuse the vtable (there is no
guarantee that the vtable exists anyway.)


I never heard of
any general-purpose pattern-implementation, there always was some
case where the implementation couldn't be used for making use of
the pattern it's supposed to implement...

You should check out "Modern C++ Design: Generic Programming and Design
Patterns Applied" by Andrei Alexandrescu


--
Magic depends on tradition and belief. It does not welcome observation,
nor does it profit by experiment. On the other hand, science is based
on experience; it is open to correction by observation and experiment.
.



Relevant Pages

  • Re: Maintance of c++ code
    ... iterator constant in the standard, ... template ... template <class Iter_> ... container, some only work with particular containers. ...
    (comp.object)
  • Re: Errors in VC program with gdiplus
    ... template argument list ... : see declaration of 'iterator' ...
    (microsoft.public.vc.mfc)
  • Errors in VC program with gdiplus
    ... template argument list ... : see declaration of 'iterator' ...
    (microsoft.public.vc.mfc)
  • Re: Einfache Frage zu STD::SORT
    ... > void sort(RanIt first, RanIt last, Pred pr); ... > The first template function reorders the sequence designated by ... >> auch was anderes, je nachdem halt, wie der jeweilige Iterator ... Eben, ein Objekt. ...
    (microsoft.public.de.vc)
  • const overload
    ... MathematicalSet is to be a class template that's supposed to behave as the ... Then, if the iterator for the MathematicalSet template is defined properly, ... modify that object is flagged by the compiler at compile time as faulty. ... bob b, b1, b2; ...
    (comp.lang.cpp)