473,396 Members | 1,929 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,396 software developers and data experts.

Intrinsic Array as a Container


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 constrbegin() const { return arr+(len-1); }
NON_CONST_IN_TERMS_OF(ReversePtr<T>,rbegin(),rbegi n())

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

T const &operator[](size_type const i) const {return arr[i];}
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[i];
}
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.

--

Frederick Gotham
Nov 22 '06 #1
16 1819
Frederick Gotham wrote:
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)
Nov 22 '06 #2
On Wed, 22 Nov 2006 14:44:47 GMT, Frederick Gotham wrote:
>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
Nov 22 '06 #3
Frederick Gotham wrote:
>
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.
Use std::reverse_iterator<T*instead. It has the advantage of not requiring
UB in implementing rend(). (See below)
>
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 constrbegin() const { return arr+(len-1); }
NON_CONST_IN_TERMS_OF(ReversePtr<T>,rbegin(),rbegi n())

ReversePtr<T constrend() const { return arr-1; }
UB: there is no "before-begin" pointer. (I know, it's a shame.)

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

T const &operator[](size_type const i) const {return arr[i];}
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[i];
}
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[i] = 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[i] = *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[i] = 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

Nov 22 '06 #4
Roland Pibinger:
C++ is powerful enough to avoid that kind of macros.

Can you please show me how?

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

begin(), end() return (const_)iterators

Yes, which is why I have the functions return "T const*".

--

Frederick Gotham
Nov 23 '06 #5
On Thu, 23 Nov 2006 19:21:43 GMT, Frederick Gotham wrote:
>Roland Pibinger:
> begin(), end() return (const_)iterators

Yes, which is why I have the functions return "T const*".
But you should return iterator and const_iterator.
Nov 23 '06 #6
Roland Pibinger wrote:
On Thu, 23 Nov 2006 19:21:43 GMT, Frederick Gotham wrote:
>>Roland Pibinger:
>> begin(), end() return (const_)iterators

Yes, which is why I have the functions return "T const*".

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
Nov 23 '06 #7
On Thu, 23 Nov 2006 17:52:41 -0500, Kai-Uwe Bux wrote:
>Roland Pibinger wrote:
>On Thu, 23 Nov 2006 19:21:43 GMT, Frederick Gotham wrote:
>>>Roland Pibinger:
begin(), end() return (const_)iterators
Yes, which is why I have the functions return "T const*".
But you should return iterator and const_iterator.

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
Nov 24 '06 #8
Roland Pibinger wrote:
On Thu, 23 Nov 2006 17:52:41 -0500, Kai-Uwe Bux wrote:
>>Roland Pibinger wrote:
>>On Thu, 23 Nov 2006 19:21:43 GMT, Frederick Gotham wrote:
Roland Pibinger:
begin(), end() return (const_)iterators
Yes, which is why I have the functions return "T const*".
But you should return iterator and const_iterator.

They do. From the original post:

typedef T *iterator;
typedef T const *const_iterator;

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
Nov 24 '06 #9
On Fri, 24 Nov 2006 05:57:07 -0500, Kai-Uwe Bux wrote:
>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
Nov 24 '06 #10
Roland Pibinger wrote:
On Fri, 24 Nov 2006 05:57:07 -0500, Kai-Uwe Bux wrote:
>>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.
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.

>>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.
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<Tas 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
Nov 24 '06 #11

Kai-Uwe Bux wrote:
Roland Pibinger wrote:
On Fri, 24 Nov 2006 05:57:07 -0500, Kai-Uwe Bux wrote:
>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.

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

Nov 24 '06 #12
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.

--

Frederick Gotham
Nov 24 '06 #13
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*".

--

Frederick Gotham
Nov 24 '06 #14
Gavin Deane wrote:
>
Kai-Uwe Bux wrote:
>Roland Pibinger wrote:
On Fri, 24 Nov 2006 05:57:07 -0500, Kai-Uwe Bux wrote:
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.

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.
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?

>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.
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
Nov 24 '06 #15

Frederick Gotham wrote:
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.
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

Nov 24 '06 #16

Kai-Uwe Bux wrote:
Gavin Deane wrote:
Kai-Uwe Bux wrote:
Roland Pibinger wrote:
On Fri, 24 Nov 2006 05:57:07 -0500, Kai-Uwe Bux wrote:
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.
<snip>
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 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 <vectorheader 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 <vectorheader 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.
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?
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

Nov 25 '06 #17

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

5
by: Jeremy Cowles | last post by:
I have been reading a book that focuses on understanding the intrinsic types of C++ in depth. The author's mentality is this: "Understand the intrinsic types, then learn the std types as needed...
9
by: justanotherguy63 | last post by:
Hi, I am designing an application where to preserve the hierachy and for code substitability, I need to pass an array of derived class object in place of an array of base class object. Since I...
29
by: shmartonak | last post by:
For maximum portability what should the type of an array index be? Can any integer type be used safely? Or should I only use an unsigned type? Or what? If I'm using pointers to access array...
13
by: Alek Davis | last post by:
Hi, Is it possible to access intrinsic ASP objects, such as Request, from a .NET class. Say, I have a .NET library exposed via a COM or COM+ wrapper. Can this library retrieve the request info...
4
by: bienwell | last post by:
Hi all, Data displayed on the datalist control is bound by the column name of the dataset like this : <%# DataBinder.Eval(Container.DataItem, "title")%> Could I use an element of the array...
6
by: Paminu | last post by:
If I have this struct: #include <stdlib.h> #include <stdio.h> #define KIDS 4 typedef struct test { int x; int y; } container;
18
by: toton | last post by:
Hi, In C++ when I initialize an array it, also initializes the class that it contains, which calls the default constructor. However, I want to initialize the array only (i.e reserve the space) and...
4
by: De_Kabal | last post by:
I'm trying to bind a 12x16 array to a repeater to display the information in a table to have certain format. Right now all it does is display all the array elements on individual rows. Does anyone...
7
by: Unite | last post by:
I have a ArrayList which I add a Array to. How will I access the array from the ArrayList? Example : /*Storing the array*/ String Container = new String; ArrayList arr =new ArrayList(); ...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.