Intrinsic Array as a Container

F

Frederick Gotham

Inspired by page 219 of Nicolai M. Josuttis's book, I set out to write a
class for an intrinsic array which would behave, to as far an extent as
possible, like a container. Also though, I wanted no overhead whatsoever.

The code I'm about to show below is not finished, it may contain the odd
oversight, bug or error (but at a quick glance it seems OK).

First of all though, I want to show you some macros I've written. The
purpose of the macros is to:

(1) Reduce repetitive typing.
(2) Reduce the possibility for error.

Before you use these macros though, you must a typedef to your class, e.g.:

class SmartPointerOrWhatever {
typedef SmartPointerOrWhatever ThisClass;
};

The first macro, "POST_IN_TERMS_OF", expands to a definition of a post-
operator which invokes the corresponding pre-operator. Here it is:

#define POST_IN_TERMS_OF(op) \
ThisClass operator op(int) \
{ \
ThisClass const temp(*this); \
op *this; \
return temp; \
}

The second macro, "OP_IN_TERMS_OF", expands to a definition of an operator
such as +, which works off +=. Here it is:

#define OP_IN_TERMS_OF(op,param_dec,obj) \
ThisClass operator op(param_dec) const \
{ \
ThisClass temp(*this); \
temp op##= obj; \
return temp; \
}

(I realise that this won't work if "param_dec" were to contain a comma.)

The third macro, "NON_CONST_IN_TERMS_OF", expands to a definition of a non-
const member function (or member function operator) which calls the const
version. Here it is:

#define NON_CONST_IN_TERMS_OF(ret,func_dec,call) \
ret func_dec \
{ \
return \
const_cast<ret>( \
const_cast<ThisClass const*>(this)-> call \
); \
}

(I realise that this won't work if "func_dec" were to contain a comma.)

First of all I'd like to ask, what do you think of these macros? Has anyone
ever done something similar? You can see them in action now below as I
implement "IntrinsicArray". Before that though, I'm going to implement a
reverse pointer for use as a reverse iterator:

#include <cstddef>

template<class T>
struct ReversePtr {

typedef ReversePtr ThisClass;
typedef std::size_t size_t;

T *p;

ReversePtr(T *const arg) : p(arg) {}

operator T*() { return p; }
operator T const *() const { return p; }

ReversePtr &operator++() { --p; return *this; }
ReversePtr &operator--() { ++p; return *this; }

POST_IN_TERMS_OF(++)
POST_IN_TERMS_OF(--)

ReversePtr &operator+=(size_t const i) { p -= i; return *this; }
ReversePtr &operator-=(size_t const i) { p += i; return *this; }

OP_IN_TERMS_OF(+,size_t const i,i)
OP_IN_TERMS_OF(-,size_t const i,i)

T const &operator[](size_t const i) const { return *(p-i); }
NON_CONST_IN_TERMS_OF(T&,operator[](size_t const i),operator[](i))
};

What do you think of ReversePtr? I appreciate any suggestions.

Now here's IntrinsicArray:

#include <cstddef>
#include <stdexcept>

template<class T,std::size_t len>
struct IntrinsicArray {

typedef IntrinsicArray ThisClass;

T arr[len];

typedef T value_type;
typedef T *iterator;
typedef T const *const_iterator;
typedef T &reference;
typedef T const &const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;

T const *begin() const { return arr; }
NON_CONST_IN_TERMS_OF(T*,begin(),begin())

T const *end() const { return arr+len; }
NON_CONST_IN_TERMS_OF(T*,end(),end())

ReversePtr<T const> rbegin() const { return arr+(len-1); }
NON_CONST_IN_TERMS_OF(ReversePtr<T>,rbegin(),rbegin())

ReversePtr<T const> rend() const { return arr-1; }
NON_CONST_IN_TERMS_OF(ReversePtr<T>,rend(),rend())

T const &operator[](size_type const i) const {return arr;}
NON_CONST_IN_TERMS_OF(T&,operator[](size_type const i),operator[](i))

T const &at(size_type const i) const
{
if (i >= len) throw std::range_error();
return arr;
}
NON_CONST_IN_TERMS_OF(T&,at(size_type const i),at(i))

T const &front() const { return *arr; }
NON_CONST_IN_TERMS_OF(T&,front(),front())

T const &back() const { return arr[len-1]; }
NON_CONST_IN_TERMS_OF(T&,back(),back())

size_type size() const { return len; }
bool empty() const { return false; }
size_type capacity() const { return len; }
size_type max_size() const { return len; }
};

Of course, the code isn't finished yet, but I welcome any comments,
questions, or suggestions.
 
P

Pete Becker

Frederick said:
Inspired by page 219 of Nicolai M. Josuttis's book, I set out to write a
class for an intrinsic array which would behave, to as far an extent as
possible, like a container. Also though, I wanted no overhead whatsoever.

Sounds suspiciously like std::tr1::array.

--

-- Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com)
Author of "The Standard C++ Library Extensions: a Tutorial and
Reference." (www.petebecker.com/tr1book)
 
R

Roland Pibinger

Inspired by page 219 of Nicolai M. Josuttis's book, I set out to write a
class for an intrinsic array which would behave, to as far an extent as
possible, like a container. Also though, I wanted no overhead whatsoever.

You mean this?
http://www.josuttis.com/libbook/cont/carray.hpp.html
First of all though, I want to show you some macros I've written. [...]
First of all I'd like to ask, what do you think of these macros?

C++ is powerful enough to avoid that kind of macros.
Before that though, I'm going to implement a
reverse pointer for use as a reverse iterator:

#include <cstddef>

template<class T>
struct ReversePtr {

What about std::reverse_iterator?
Now here's IntrinsicArray:

#include <cstddef>
#include <stdexcept>

template<class T,std::size_t len>
struct IntrinsicArray {

typedef IntrinsicArray ThisClass;

T arr[len];
public?

typedef T value_type;
typedef T *iterator;
typedef T const *const_iterator;
typedef T &reference;
typedef T const &const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;

T const *begin() const { return arr; }

begin(), end() return (const_)iterators
Of course, the code isn't finished yet, but I welcome any comments,
questions, or suggestions.

Hmm, well ...

Best wishes,
Roland Pibinger
 
K

Kai-Uwe Bux

Frederick said:
Inspired by page 219 of Nicolai M. Josuttis's book, I set out to write a
class for an intrinsic array which would behave, to as far an extent as
possible, like a container. Also though, I wanted no overhead whatsoever.

The code I'm about to show below is not finished, it may contain the odd
oversight, bug or error (but at a quick glance it seems OK).

First of all though, I want to show you some macros I've written. The
purpose of the macros is to:

(1) Reduce repetitive typing.
(2) Reduce the possibility for error.

Before you use these macros though, you must a typedef to your class,
e.g.:

class SmartPointerOrWhatever {
typedef SmartPointerOrWhatever ThisClass;
};

The first macro, "POST_IN_TERMS_OF", expands to a definition of a post-
operator which invokes the corresponding pre-operator. Here it is:

#define POST_IN_TERMS_OF(op) \
ThisClass operator op(int) \
{ \
ThisClass const temp(*this); \
op *this; \
return temp; \
}

The second macro, "OP_IN_TERMS_OF", expands to a definition of an operator
such as +, which works off +=. Here it is:

#define OP_IN_TERMS_OF(op,param_dec,obj) \
ThisClass operator op(param_dec) const \
{ \
ThisClass temp(*this); \
temp op##= obj; \
return temp; \
}

(I realise that this won't work if "param_dec" were to contain a comma.)

The third macro, "NON_CONST_IN_TERMS_OF", expands to a definition of a
non- const member function (or member function operator) which calls the
const version. Here it is:

#define NON_CONST_IN_TERMS_OF(ret,func_dec,call) \
ret func_dec \
{ \
return \
const_cast<ret>( \
const_cast<ThisClass const*>(this)-> call \
); \
}

(I realise that this won't work if "func_dec" were to contain a comma.)

First of all I'd like to ask, what do you think of these macros? Has
anyone ever done something similar? You can see them in action now below
as I implement "IntrinsicArray". Before that though, I'm going to
implement a reverse pointer for use as a reverse iterator:

#include <cstddef>

template<class T>
struct ReversePtr {

typedef ReversePtr ThisClass;
typedef std::size_t size_t;

T *p;

ReversePtr(T *const arg) : p(arg) {}

operator T*() { return p; }
operator T const *() const { return p; }

ReversePtr &operator++() { --p; return *this; }
ReversePtr &operator--() { ++p; return *this; }

POST_IN_TERMS_OF(++)
POST_IN_TERMS_OF(--)

ReversePtr &operator+=(size_t const i) { p -= i; return *this; }
ReversePtr &operator-=(size_t const i) { p += i; return *this; }

OP_IN_TERMS_OF(+,size_t const i,i)
OP_IN_TERMS_OF(-,size_t const i,i)

T const &operator[](size_t const i) const { return *(p-i); }
NON_CONST_IN_TERMS_OF(T&,operator[](size_t const i),operator[](i))
};

What do you think of ReversePtr? I appreciate any suggestions.


Now here's IntrinsicArray:

#include <cstddef>
#include <stdexcept>

template<class T,std::size_t len>
struct IntrinsicArray {

typedef IntrinsicArray ThisClass;

T arr[len];

typedef T value_type;
typedef T *iterator;
typedef T const *const_iterator;
typedef T &reference;
typedef T const &const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;

T const *begin() const { return arr; }
NON_CONST_IN_TERMS_OF(T*,begin(),begin())

T const *end() const { return arr+len; }
NON_CONST_IN_TERMS_OF(T*,end(),end())

ReversePtr<T const> rbegin() const { return arr+(len-1); }
NON_CONST_IN_TERMS_OF(ReversePtr<T>,rbegin(),rbegin())

ReversePtr<T const> rend() const { return arr-1; }

UB: there is no "before-begin" pointer. (I know, it's a shame.)

You could just use std::reverse_iterator said:
NON_CONST_IN_TERMS_OF(ReversePtr<T>,rend(),rend())

T const &operator[](size_type const i) const {return arr;}
NON_CONST_IN_TERMS_OF(T&,operator[](size_type const i),operator[](i))

T const &at(size_type const i) const
{
if (i >= len) throw std::range_error();
return arr;
}
NON_CONST_IN_TERMS_OF(T&,at(size_type const i),at(i))

T const &front() const { return *arr; }
NON_CONST_IN_TERMS_OF(T&,front(),front())

T const &back() const { return arr[len-1]; }
NON_CONST_IN_TERMS_OF(T&,back(),back())

size_type size() const { return len; }
bool empty() const { return false; }
size_type capacity() const { return len; }
size_type max_size() const { return len; }
};

Of course, the code isn't finished yet, but I welcome any comments,
questions, or suggestions.


Reminds me of std::tr1::array. It also reminds me of my own try (posted just
for inspiration):

template < typename T, std::size_t the_size >
class array {
private:

typedef T T_array [the_size];
typedef T_array & T_array_ref;
typedef T_array const & T_array_const_ref;

T_array a_ptr;

public:

// Typedefs
// ========

typedef T value_type;
typedef value_type * pointer;
typedef value_type const * const_pointer;
typedef value_type & reference;
typedef value_type const &
const_reference;
typedef pointer_iterator< value_type,
pointer,
reference > iterator;
typedef pointer_iterator< const value_type,
const_pointer,
const_reference > const_iterator;
typedef typename std::reverse_iterator< iterator >
reverse_iterator;
typedef typename std::reverse_iterator< const_iterator >
const_reverse_iterator;
typedef std::size_t size_type;
typedef std::ptrdiff_t
difference_type;


// Construction / Destruction
// ==========================

array ( void ) {}

array ( const_reference t ) {
for ( std::size_t i = 0; i < the_size; ++ i ) {
a_ptr = t;
}
}

array ( T_array_const_ref c_ref )
: a_ptr ( c_ref )
{}

array ( array const & other ) {
for ( size_type index = 0; index < the_size; ++index ) {
a_ptr[index] = other.a_ptr[index];
}
}

template < typename ConstIterType >
array ( ConstIterType from, ConstIterType to ) {
std::size_t i = 0;
for ( ConstIterType iter = from; iter != to; ++ iter ) {
if ( i < the_size ) {
a_ptr = *iter;
++ i;
} else {
// flag_range_error( __FILE__, __LINE__, "argument list too long" );
return;
}
}
while ( i < the_size ) {
// flag_range_error( __FILE__, __LINE__, "argument list too short" );
// return;
a_ptr = value_type();
++i;
}
}

// Conversion
// ==========

operator T_array_ref () {
return( a_ptr );
}


// Assignment
// ==========

array const & operator= ( array const & other ) {
for ( size_type index = 0; index < the_size; ++index ) {
a_ptr[index] = other.a_ptr[index];
}
return( *this );
}

void swap ( array & other ) {
std::swap_ranges( this->begin(), this->end(), other.begin() );
}


// Access
// ======

reference at ( std::size_t pos ) {
if ( the_size <= pos ) {
flag_range_error( __FILE__, __LINE__, "index out of range" );
}
return( a_ptr[pos] );
}


const_reference at ( std::size_t pos ) const {
if ( the_size <= pos ) {
flag_range_error( __FILE__, __LINE__, "index out of range" );
}
return( a_ptr[pos] );
}

reference operator[] ( std::size_t pos ) {
return( a_ptr[pos] );
}

const_reference operator[] ( std::size_t pos ) const {
return( a_ptr[pos] );
}

reference front ( void ) {
return( a_ptr[0] );
}


const_reference front ( void ) const {
raturn( a_ptr[0] );
}


reference back ( void ) {
return( a_ptr[the_size-1] );
}


const_reference back ( void ) const {
return( a_ptr[the_size-1] );
}



// About
// =====

size_type size ( void ) const {
return( the_size );
}


bool empty ( void ) const {
return( the_size == 0 );
}


// Iterators
// =========

iterator begin ( void ) {
return( a_ptr );
}


const_iterator begin ( void ) const {
return( a_ptr );
}


iterator end ( void ) {
return( a_ptr + the_size );
}


const_iterator end ( void ) const {
return( a_ptr + the_size );
}

reverse_iterator rbegin ( void ) {
return( reverse_iterator( this->end() ) );
}

const_reverse_iterator rbegin ( void ) const {
return( reverse_iterator( this->end() ) );
}

reverse_iterator rend ( void ) {
return( reverse_iterator( this->begin() ) );
}

const_reverse_iterator rend ( void ) const {
return( reverse_iterator( this->begin() ) );
}

}; // array< T, the_size >

template < typename T, std::size_t N >
void swap ( array< T, N > & a, array< T, N > & b ) {
a.swap(b);
}




Best

Kai-Uwe Bux
 
K

Kai-Uwe Bux

Roland said:
But you should return iterator and const_iterator.

They do. From the original post:

typedef T *iterator;
typedef T const *const_iterator;

...

T const *begin() const { return arr; }
NON_CONST_IN_TERMS_OF(T*,begin(),begin())

T const *end() const { return arr+len; }
NON_CONST_IN_TERMS_OF(T*,end(),end())



Best

Kai-Uwe Bux
 
R

Roland Pibinger

They do. From the original post:

typedef T *iterator;
typedef T const *const_iterator;

But where are those typedefs used?

IMO, there are two possibilities:
1. Apply STL compliant style and use iterators which may be pointers
but may also be templates, e.g. in a (debug-)checked implementation.
2. Write an 'array template' that mimics a C++ array. In that case
avoid the STL related (typedef) stuff and use only pointers.

BTW, the implementation would be more useful if it were a 'maximum
size' instead of an 'always full' container.

Best wishes,
Roland Pibinger
 
K

Kai-Uwe Bux

Roland said:
But where are those typedefs used?

In client code, of course. As with the STL containers, the point of the
typedefs is just that clients do not need to know how iterators are
implemented. However, in implementing the array class, the programmer has
no reason to pretend that he did not know how he decided to implement
iterators.

IMO, there are two possibilities:
1. Apply STL compliant style and use iterators which may be pointers
but may also be templates, e.g. in a (debug-)checked implementation.

That is exactly what the code is doing. A different implementation is
perfectly free to say

class iterator { ... };
iterator begin() { ... };

instead; and client code written generically will not see the difference.

You seem to think that there is a difference between:

typedef T* iterator;
T* begin() { ... }

and:

typedef T* iterator;
iterator begin() { ... }

But there is none.

STL-style written client code will always use the
IntrinsicArray<T>::iterator typedef. Relying on iterator = T* is a bug in
client code [the classical one of relying on an implementation detail] but
not a bug in the implementation. Of course, I am assuming here that the
documentation of IntrinsicArray, which has not been posted, does not
guarantee that iterator = T*.

2. Write an 'array template' that mimics a C++ array. In that case
avoid the STL related (typedef) stuff and use only pointers.

The OP decided for the other option.

BTW, the implementation would be more useful if it were a 'maximum
size' instead of an 'always full' container.

Maybe, but that would introduce the need for a size variable which, in the
eyes of the OP, is likely to qualify as overhead.



Best

Kai-Uwe Bux
 
R

Roland Pibinger

You seem to think that there is a difference between:

typedef T* iterator;
T* begin() { ... }

and:

typedef T* iterator;
iterator begin() { ... }

But there is none.

Of course there is a difference! When a function returns T* one uses a
T* variable to capture the return value, when a function returns an
iterator an iterator variable is used.
STL-style written client code will always use the
IntrinsicArray<T>::iterator typedef.

Nope. C/C++ clients can rely on the type of the return value because
that type is part of the function contract.

Best wishes,
Roland Pibinger
 
K

Kai-Uwe Bux

Roland said:
Of course there is a difference! When a function returns T* one uses a
T* variable to capture the return value, when a function returns an
iterator an iterator variable is used.

Typedefs introduce aliases. Thus, a variable of type T* and a variable of
type IntrinsicArray<T>::iterator have the same type. The only difference is
in your head, not in the code. You are mistaking the implementation for the
documentation. The docs could (maybe should) state that the return type of
begin() is iterator. The implementation, however, is free to make up to
that promise in any way it pleases.

Nope. C/C++ clients can rely on the type of the return value because
that type is part of the function contract.

a) C++ does not support contracts in code. The contract is defined in the
documentation. We have not seen that. I already said that I would specify
iterator in the docs. However, one could, of course, make the guarantee
that iterator = T* part of the specification.

b) If you use the IntrinsicArray<T> as defined, then your code will not
break even if you use T*. Using iterator is still wise and makes your code
easier to change when you want to switch containers. But that's all.


Best

Kai-Uwe Bux
 
G

Gavin Deane

Kai-Uwe Bux said:
Typedefs introduce aliases. Thus, a variable of type T* and a variable of
type IntrinsicArray<T>::iterator have the same type. The only difference is
in your head, not in the code.

Of course the difference is only in a human reader's head, but then
that's the only place that matters. There are always countless
different ways to express identical intent to the compiler and the
compiler will cope with all of them equally. Your job as a programmer
is to select, from all those possiblities, the one that most clearly
expresses your intent to a human being.

It is not clear from the first example whether begin() is intended to
return a T* because that is what iterator is typedef'd to be, or
whether begin() is intended to return a T* regardless of what type
iterator is.

In the second example, it is clear that begin() is meant to return an
iterator, whatever type that is.
You are mistaking the implementation for the
documentation. The docs could (maybe should) state that the return type of
begin() is iterator. The implementation, however, is free to make up to
that promise in any way it pleases.

I would argue that, at best, the at-first-glance mistmatch between the
documentation and your first code example needlessly introduces the
risk of confusing the reader for zero gain.

There is the other point that, as the maintainer of the class, if I
need to refactor the class to use a different type for iterator, or
maybe just a different type for a debug build, your first example makes
it harder for me to do that. Not very much harder necessarily,
depending how many function signatures use T* when they mean iterator,
but still needless extra complication for zero gain.

Gavin Deane
 
F

Frederick Gotham

Roland Pibinger:
Of course there is a difference! When a function returns T* one uses a
T* variable to capture the return value, when a function returns an
iterator an iterator variable is used.


int Func1() { return 7; }

typedef int MyInt;

MyInt Func2() { return 7; }

int main()
{
int a = Func1();
MyInt b = Func1();

int c = Func2();
MyInt d = Func2();

a = b = c = d;
}

If that doesn't compile, burn your compiler.
 
F

Frederick Gotham

Gavin Deane:
Of course the difference is only in a human reader's head, but then
that's the only place that matters. There are always countless
different ways to express identical intent to the compiler and the
compiler will cope with all of them equally. Your job as a programmer
is to select, from all those possiblities, the one that most clearly
expresses your intent to a human being.


We will have to simply disagree on this.

I could have written "const_iterator" instead of "T const*", but I prefer the
latter as it's more tangible in my own mind. If you would prefer to write the
former, then fair enough that's your choice.

If I were to write documentation for "IntrinsicArray", I might choose to list
the function signatures as returning "const_iterator" instead of "T const*".
 
K

Kai-Uwe Bux

Gavin said:
Of course the difference is only in a human reader's head, but then
that's the only place that matters. There are always countless
different ways to express identical intent to the compiler and the
compiler will cope with all of them equally. Your job as a programmer
is to select, from all those possiblities, the one that most clearly
expresses your intent to a human being.

The point is which human being we are talking about. Mr Pibinger, I took it,
was talking about a programmer of client code. Such programmer, in my
opinion, should not have any need to read the code for IntrinsicArray<>;
and there is no promise/contract/intent in the code conveyed to the client
programmer.

I grant you that the two variations of the code are not equivalent in
quality: the difference lies in ease of maintainance. There, you have a
point about conveying intent.

It is not clear from the first example whether begin() is intended to
return a T* because that is what iterator is typedef'd to be, or
whether begin() is intended to return a T* regardless of what type
iterator is.

In the second example, it is clear that begin() is meant to return an
iterator, whatever type that is.

The point where to make clear what begin() is supposed to return, is the
specs/docs for this class, not the code. If I open the header files for my
STL implementation of <vector>, I might find that begin() returns something
of type

whatever_fancy_iterator_facade< value_type * >

where the standard says that begin() returns iterator. Should I believe the
standard or the header file?

I would argue that, at best, the at-first-glance mistmatch between the
documentation and your first code example needlessly introduces the
risk of confusing the reader for zero gain.

True.
There is the other point that, as the maintainer of the class, if I
need to refactor the class to use a different type for iterator, or
maybe just a different type for a debug build, your first example makes
it harder for me to do that. Not very much harder necessarily,
depending how many function signatures use T* when they mean iterator,
but still needless extra complication for zero gain.

Here, you are correct, too. I was not arguing that the two examples are
equal in code quality. I was arguing that they are equivalent in observable
behavior and make the same promisses to any client (namely none since, in
this case, promisses to clients have to be part of the docs/specs).



Best

Kai-Uwe Bux
 
G

Gavin Deane

Frederick said:
Gavin Deane:


We will have to simply disagree on this.

As long as I never have to maintain code you wrote, that approach is
fine by me.
I could have written "const_iterator" instead of "T const*", but I prefer the
latter as it's more tangible in my own mind. If you would prefer to write the
former, then fair enough that's your choice.

The difference to me is that, if, conceptually, the function returns an
object that can be used to iterate through the sequence and can be
used, by dereferencing, to obtain an immutable object in that sequence,
then C++ has a name for such a concept and that name is const_iterator.
The fact that, at the particular point in time you wrote the code, the
underlying type you chose to model the const_iterator concept was a T
const* is incidental. It is an implementation detail. To confuse the
abstract concept with the implementation detail is indicative of a lack
of clarity of thinking.
If I were to write documentation for "IntrinsicArray", I might choose to list
the function signatures as returning "const_iterator" instead of "T const*".

Then you still introduce the maintenance problem I described.

Gavin Deane
 
G

Gavin Deane

Kai-Uwe Bux said:
The point is which human being we are talking about. Mr Pibinger, I took it,
was talking about a programmer of client code. Such programmer, in my
opinion, should not have any need to read the code for IntrinsicArray<>;
and there is no promise/contract/intent in the code conveyed to the client
programmer.

I was indeed talking about the human being who has to read the code. As
a client of std::vector, I have no more need to be able to comprehend
the code in my <vector> header than my Grandmother does. I look in my
copy of the standard or my copy of Josuttis to find out what interface
std::vector provides. I don't look in the <vector> header for that
information because I expect my standard library supplier to implement
the standard correctly or to documnet the instances where they don't
and as long as they do that, I don't care how they do it.

So let me rephrase:
Your job as a programmer is to select, from all those possiblities, the
one that most clearly expresses your intent to the human beings who
need to read your code.
I grant you that the two variations of the code are not equivalent in
quality: the difference lies in ease of maintainance. There, you have a
point about conveying intent.


The point where to make clear what begin() is supposed to return, is the
specs/docs for this class, not the code. If I open the header files for my
STL implementation of <vector>, I might find that begin() returns something
of type

whatever_fancy_iterator_facade< value_type * >

where the standard says that begin() returns iterator. Should I believe the
standard or the header file?

If you provide me with a class to use, I absolutely agree with you
that, to understand how to use that class I should look in the
documentation you provide, not in the code. If the code does not behave
as documented, the solution is to ask for a refund.

However, I was coming from the perspective of your successor on the
project. If you move on to bigger and brighter things and I pick up
your code and documentation to maintain, it is very important to me
(and therefore to my employer if we assume a professional context) that
your code and documentation are easy to comprehend.

<snip>

Gavin Deane
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top