Can we override [][] ?

N

Noah Roberts

Axter said:
The purpose of the dynamic_2d_array, is to give a dynamic version of a
static size C-style array.
It's no more or no less safer then using a static C-Style array, and
therefore, no more horrid then using a static size C-Style array.

Actually it is. As you said, it offers nothing over using a C array
and therefor is pointless extra code that serves no purpose.
If you need something that requires bounds checking, then you should
not use it.
The class is not intended for that purpose.

It should at least provide access to the bounds it is tracking so
clients don't have to wrap it in a class that does.
These are example skeleton classes. They don't have to be complete.

They don't even qualify as "example skeleton classes" and that is at
least not how the first is being advertized.
They just need enough to get the point across as to how to get a [][]
interface.

In a piss poor and dangerous manner.
I heard this from most programmers, who don't have the experience in
actually testing this theory out.
I've conducted test, shows your above comment to be false.

Interesting, then why do you yourself make it?

Besides, if your tests are anything like your example code then they
are completely meaningless. If they test your example code they are
equally so.
The implementations I posted as examples, are reasonable for their
purpose.

I pointed out several reasons they are not...you haven't answered those
reasons.
And again, they're no more dangerous then using static size C-Style
arrays.

Correct. They are also no less so. In fact the first one provides
absolutely no benefits over a simple array whatsoever. The second
provides no benefits over vector<vector<>> and in fact removes access
to important information that would be available by just using
vector said:
It's incorrect to believe that all code requires bounds checking.

And I never said it was.
That's why the C++ standard doesn't require that STL have bounds
checking.
If' it's good for the standard, then it's good for a skeleton example
implementation.

Every single container class in the standard library has a size()
member function and in fact at least one of the containers does provide
bounds checking. Another initializes new contents on access to a non
existant index... All STL container classes also provide several
protective abstractions surrounding the internal implementation
including iterators and reference proxies. Yes, if you know what a
particular implementation looks like you can get past those as they are
only typedefs but still, it is an abstraction that can be used in a
safe and consistant manner. Your classes don't do any of these
things...your classes are sluts.

Yes, these classes are a far cry short of the STL.
Following an FAQ blindly, is a mistake.
This C++ FAQ is created by one person, and he is no more or no less
prone to mistakes then any other experience programmer.

Yes, following the FAQ blindly is a mistake. I've seen several
examples of implementing [][] that are not the newbie hackery that
yours is. I prefer to listen until I find a compelling reason not
to...your reasoning and your examples are far from compelling and in
fact are compelling arguments for the FAQ as your forcing of [][] on
this concept has led you to create monsters.
IMHO, this FAQ is wrong, and I posted alternative skeleton examples
that can be used as a template for user's custom matrix class.

Well, I've given several reasons why they don't qualify for that. They
need major revisions.
IMHO, your nonconstructive comments do a disservice to the C++
community and to this newsgroup.

You may see them as noncunstructive if you want. I am not surprised as
you continue to pass these off as "expert guidance" and so wouldn't
want to hear that they actually exemplify several horrendous beginner
mistakes but whatever.
 
N

Noah Roberts

Jakob said:
What is this 'something' you are referring to?

That your code shouldn't be depending on what type your objects are so
long as they respond to the interface you expect them to.

And how does it
relate to using "C" as a prefix for class names?

C as in "this is a class" indicates that the programmer wants to know
if the types he is working with are classes or not...but his code
shouldn't care and therefore he/she shouldn't care either. Also what
happens when you decide that a class isn't the best implementation but
maybe it should just be a struct instead. You gonna change to strName
all over the damn place?

Hungarian notation is a bastard child that should have been aborted.
Too often I see code written in it where someone said, "Hey, I need
another variation of that true/false value so I'll change to char," but
then they didn't want to have to change the name all over the place so
they left it as bFlag instead of iFlag.
 
A

Axter

Noah said:
Axter wrote:

You may see them as noncunstructive if you want. I am not surprised as
you continue to pass these off as "expert guidance" and so wouldn't
want to hear that they actually exemplify several horrendous beginner
mistakes but whatever.

The example skeleton classes will remain as is, since they serve their
purpose, whether you want to believe it or not.

If you think your comments are constructive, then you're either
ignorant, or have no manners. IMHO, it's most likely both.

Good day.... I said Good day!!! ;-)
 
E

Earl Purple

Axter said:
ignorant, or have no manners. IMHO, it's most likely both.

This is becoming too much of a flame war. But there was some
constructive criticism in there. Let me see if I can point it out:

template < class T>
class dynamic_2d_array
{
public:
dynamic_2d_array(size_t row, size_t col):m_row(row),m_col(col),
m_data((row!=0&&col!=0)?new T[row*col]:NULL){}
dynamic_2d_array(const
dynamic_2d_array&src):m_row(src.m_row),m_col(src.m_col),
m_data((src.m_row!=0&&src.m_col!=0)?new T[src.m_row*src.m_col]:NULL){
for(size_t r=0;r<m_row;++r)for(size_t c=0;c<m_col;++c) (*this)[r][c]
= src[r][c];
}
~dynamic_2d_array(){if(m_data) delete []m_data;}
inline T* operator[](size_t i) {return (m_data + (m_col*i));}
inline T const*const operator[](size_t i) const {return (m_data +
(m_col*i));}
protected:
dynamic_2d_array& operator=(const dynamic_2d_array&);
private:
const size_t m_row;
const size_t m_col;
T* m_data;
};

1. No method to retrieve back the dimensions. Easy enough to modify.
Add in:

size_t rows() const { return m_row; }
size_t cols() const { return m_col }

2. protected section when class cannot be inherited. Minor detail but
operator= is automatically disabled for this class anyway due to it
having const members.

3. no need to check for NULL in destructor

4. To access the data you call matrix[0] which is unclear notation.

5. Your copy constructor is probably slower than you think as you are
calculating the position a lot of times. But as the arrays are
identical in size you can do member-by-member copy. If you're really
advanced you'll have an is_pod<> traits-style function and use memcpy
when is_pod returns true.

I would actually say it is a bit inconsistent to allow
copy-construction but not assignment. Whilst you cannot resize,
assignment might be useful, and could be "optimised" when the two
matrices already the same dimensions by not doing a new allocation. In
fact, if the capacity of the current matrix is enough to hold the new
contents you might be able to optimise.

If you use a nested vector then resizing can be done with vector's
member resize() function.
 
N

Noah Roberts

Axter said:
The example skeleton classes will remain as is, since they serve their
purpose, whether you want to believe it or not.

Well, whatever. As examples on how to implement [][] I have to say
they are inadiquate and could easily lead a person down a bad path
because of what they omit. The answer is not as simple as you make it
out to be and considering it so can cause problems for people who might
listen to you. But in the end I guess they will learn the hard way. I
also used to force [][] into interfaces years ago when I was first
starting so I know the trouble they are in for. I learned, so will
they...some won't obviously.
 
J

Jack Saalweachter

Noah said:
"Non-standard"? Non-standard to who? Certainly not the domain which
uses subscript notation and/or M(a,b)...or the unimplmentable M[a,b].
Cute in an "oh god don't ever do that" sort of way:

struct MagicInt {
// operator overloads, constructors, etc, to make this class behave
// as an integer.
};

std::pair<MagicInt, MagicInt> operator , (const MagicInt &a, const
MagicInt& b) { return std::make_pair(a, b); }

class Array2d {
public:
value& operator[](const std::pair<MagicInt, MagicInt> &a) {
// use a.first and a.second to find the value...
}
};

int main() {
Array2d M(X, Y);

for (MagicInt a = 0; a < X; ++a)
for (MagicInt b = 0; b < Y; ++b)
M[a, b] = i + j;
}


So, 'M[a, b]' is *implementable*, even if so brittlely that you'd never
want to use it. (For instance, you cannot say 'M[1, 2]'.)



Jack Saalweachter
 
N

Noah Roberts

Earl said:
This is becoming too much of a flame war.

Yes, I suppose I am being a bit agressive but I have already pointed
out these and more problems with the code he is passing off as advice.

I also think it interesting that he claims this is a "skeleton example"
yet this is the comment in the top of that file:

// dynamic_2d_array class by David Maisonave (609-345-1007)
(www.axter.com)
// Description:
// The dynamic array class listed below is more efficient then other
// similar classes that use temporary objects as return types, or use
// an std::vector as a return type.
//
// It's also more compatible with a C style 2D array, in that the
// array is in one continuous memory block. This makes it possible
// to pass this array object to a C Function that has a C-Style
// 2D array for a parameter.
// Example usage:

Doesn't look like it is thought of as a "skeleton template" to me.
Looks like it is an attempt to document a complete class. That and the
documentation even documents a feature that isn't there, namely the
retreval of the raw data storage for use in a function requiring an
array or pointer. The fact that you CAN do this is actually a bug in
the interface that can be exploited to provide functionality that
wasn't built in.

I just hate seing stuff like this passed off as expert advice
especially when he is attempting to get beginners to move contrary to
the FAQ. Sure, the FAQ could be wrong but if your going to move
against it provide something more complete, safe, and coherent. There
can be compelling reasons that [][] needs to be in the interface but
his "expert" code is not a good way to implement it and gives the
misconception that it is simple to do, and it is far from it unless you
want to stab yourself in the ass. He compares his implementation to
the STL yet it provides none of the safety features or abstractions the
STL does. It's just bad code that exhibits bad practices (as well as
being VERY difficult to read) and I think it better to point this out.

There are people I consider experts in this field and I don't always
agree with everything they say but I acknowledge they are several
levels above me. I also write questionable code and know it...but then
I'm not calling myself an expert. Axter claims to be an expert,
exclaiming how he is top rated expert this or that on website X, but
his code says otherwise.
 
N

Noah Roberts

Jack said:
Noah said:
"Non-standard"? Non-standard to who? Certainly not the domain which
uses subscript notation and/or M(a,b)...or the unimplmentable M[a,b].
Cute in an "oh god don't ever do that" sort of way:

Wow....I bow down to your abilities to spread evil. I can only hope to
be that good someday ;)

Assuming it works, and it looks to me like it would. Ouch...
 
M

Marcus Kwok

Noah Roberts said:
Hungarian notation is a bastard child that should have been aborted.
Too often I see code written in it where someone said, "Hey, I need
another variation of that true/false value so I'll change to char," but
then they didn't want to have to change the name all over the place so
they left it as bFlag instead of iFlag.

I actually read an article just today on how Hungarian notation itself
has become horribly bastardized, and it is this bad version of it that
everybody knows and many people dislike. Essentially, in the original
paper, the author (Charles Simonyi) used the word "type" instead of
"kind", and people took it too literally, thus destroying the original
usefulness.

http://joelonsoftware.com/articles/Wrong.html
(near the bottom of the article)
 
N

Noah Roberts

Marcus said:
I actually read an article just today on how Hungarian notation itself
has become horribly bastardized, and it is this bad version of it that
everybody knows and many people dislike. Essentially, in the original
paper, the author (Charles Simonyi) used the word "type" instead of
"kind", and people took it too literally, thus destroying the original
usefulness.

http://joelonsoftware.com/articles/Wrong.html
(near the bottom of the article)

So basically it is saying things should be named based on what their
interface is no? For instance something that is a "Copier" might be
better named as a "CopierCommand" if it was in fact a "Command".
Instances of this class should reflect what they will do.

That makes sense to me but I debate whether HN is verbose enough for
that. I also believe classes and variable names should reflect what
they are meant to do but I like to be a little more verbose about it
most of the time.
 
J

Jack Saalweachter

Marcus said:
I actually read an article just today on how Hungarian notation itself
has become horribly bastardized, and it is this bad version of it that
everybody knows and many people dislike. Essentially, in the original
paper, the author (Charles Simonyi) used the word "type" instead of
"kind", and people took it too literally, thus destroying the original
usefulness.

http://joelonsoftware.com/articles/Wrong.html
(near the bottom of the article)
Huh, that article totally changed my perspective on Hungarian Notation.

The idea adding type-modifiers to a language has long seemed awesome to
me; consider if (as in the example in the article above), you said:

unsafe string a = Request("name");
...
safe string b = a; // Type error; implicit conversion from 'unsafe'
to 'safe', as illegal as modifying a 'const' object.

Knowing that Hungarian Notation was /intended/ to be a work-around for
extending the type system (since it's infinitely easier and almost as
effective), rather than a simple parroting of the existing type system,
makes it potentially useful in my mind.


Jack Saalweachter
 
A

Axter

Earl said:
Axter said:
ignorant, or have no manners. IMHO, it's most likely both.

This is becoming too much of a flame war. But there was some
constructive criticism in there. Let me see if I can point it out:

template < class T>
class dynamic_2d_array
{
public:
dynamic_2d_array(size_t row, size_t col):m_row(row),m_col(col),
m_data((row!=0&&col!=0)?new T[row*col]:NULL){}
dynamic_2d_array(const
dynamic_2d_array&src):m_row(src.m_row),m_col(src.m_col),
m_data((src.m_row!=0&&src.m_col!=0)?new T[src.m_row*src.m_col]:NULL){
for(size_t r=0;r<m_row;++r)for(size_t c=0;c<m_col;++c) (*this)[r][c]
= src[r][c];
}
~dynamic_2d_array(){if(m_data) delete []m_data;}
inline T* operator[](size_t i) {return (m_data + (m_col*i));}
inline T const*const operator[](size_t i) const {return (m_data +
(m_col*i));}
protected:
dynamic_2d_array& operator=(const dynamic_2d_array&);
private:
const size_t m_row;
const size_t m_col;
T* m_data;
};

1. No method to retrieve back the dimensions. Easy enough to modify.
Add in:

size_t rows() const { return m_row; }
size_t cols() const { return m_col }

As I previously stated, the sole purpose of the dynamic_2d_array, is to
give the functionallity of a static size C-Style array that has a
dynamic size.
Since this is easy to add, IMHO, it's not needed for the example.
2. protected section when class cannot be inherited. Minor detail but
operator= is automatically disabled for this class anyway due to it
having const members.
Good point.
3. no need to check for NULL in destructor
Another good point.
4. To access the data you call matrix[0] which is unclear notation.
I'm not sure what you mean there. Accessing an array via [][] is the
standard notation for C/C++ code.
5. Your copy constructor is probably slower than you think as you are
calculating the position a lot of times. But as the arrays are
identical in size you can do member-by-member copy.
I think it would be hard to measure the perfromance difference, but
considering it could simplify the class, it would probably be a good
idea to do a std::copy instead of the two loops.
advanced you'll have an is_pod<> traits-style function and use memcpy
when is_pod returns true.

I rather keep the class generic, and not have to rely on external
functions like boost::is_pod. I don't like creating generic code that
depends on third party libraries, even if it is boost.
You would be surprise at how many C++ developers don't even know what
boost is.
If you use a nested vector then resizing can be done with vector's
member resize() function.
I posted another link that had example code that does just that.
http://www.codeguru.com/forum/showthread.php?t=231046

The above class has a resize functoin, which I added, and it uses
vector.
I recomend using the above vector approach over my original
dynamic_2d_array class.
But if you need contiguous buffer, then the dynamic_2d_array might be a
better option.

Thanks for the constructive critique. I've already removed the op= and
the unnecessary check for NULL.
 
N

Noah Roberts

Axter said:
Thanks for the constructive critique. I've already removed the op= and
the unnecessary check for NULL.

While you're at it why don't you add some whitespace.

On the other hand...not having any definately makes your class more
obfuscated and difficult to comprehend so perhapse it is more
beneficial to beginners that you just don't. That way they won't be
able to make sense of what you are doing and will go elsewhere.
 
?

=?ISO-8859-1?Q?Martin_J=F8rgensen?=

Tomás said:
Tomás posted: -snip-
Taking Cy's trick on-board, you could change it to:


class ChessBoard {
public:

class Square {
public:
enum SquareContents {
empty, pawn, castle, horse, bishop, queen, king } contents;

Square &operator=( SquareContents const sc )
{
contents = sc;
return *this;
}
};

Square squares[64];


Square *operator[](unsigned const x)
{
return squares + 8 * (x-1);
}

};

int main()
{
ChessBoard board;

board[3][5] = ChessBoard::Square::bishop;
}

I didn't understood how [3][5] got converted to Square *, since it only
takes an unsigned const as argument?

First you create a ChessBoard with an empty SquareContents, I guess?

When you then assign something (bishop) to board[3][5] what happens then?

I understand "return squares" is element 0 to which you add an offset.
8*0 = 0, 8*1 = 8, 8*2 = 16, etc. How to insert a king in location 20?

I'm wondering, that perhaps it's connected with return *this from
&operator=, but it would be nice to see an explanation...?


Best regards
Martin Jørgensen
 
M

Marcus Kwok

Noah Roberts said:
So basically it is saying things should be named based on what their
interface is no?

I wouldn't necessarily say that it's based on their interface, but more
on their intent or purpose. For example, in the article he talks about
it in the context of a WYSIWYG word processor, which must distinguish
between coordinates relative to the window and coordinates relative to
the page layout. Prefixes start with either 'x' or 'y' (indicating
horizontal or vertical coordinate) and either 'l' or 'w' (for "layout"
or "window", respectively). Another common prefix is 'c' for "count",
so a prefix of "cb" would indicate that this variable is used to count
bytes.

Then, if anywhere in your code you see a "xl = yw" or a "yl = cb", it
should tell you that you're trying to mix logically different types,
even though all of them may be implemented as ints.
For instance something that is a "Copier" might be
better named as a "CopierCommand" if it was in fact a "Command".
Instances of this class should reflect what they will do.

I don't think this is quite the idea.
 
M

Marcus Kwok

Jack Saalweachter said:
Huh, that article totally changed my perspective on Hungarian Notation.

Yeah, me too, because before I saw this article I only knew about the
less-useful "Systems Hungarian" as opposed to the more useful "Apps
Hungarian", and thus had already discarded "[Systems] Hungarian
Notation" as not very useful.
The idea adding type-modifiers to a language has long seemed awesome to
me; consider if (as in the example in the article above), you said:

unsafe string a = Request("name");
...
safe string b = a; // Type error; implicit conversion from 'unsafe'
to 'safe', as illegal as modifying a 'const' object.

Knowing that Hungarian Notation was /intended/ to be a work-around for
extending the type system (since it's infinitely easier and almost as
effective), rather than a simple parroting of the existing type system,
makes it potentially useful in my mind.

Yes, I never really saw the value in most situations of specifying the
specific type of a variable either. The intended use gives you much
greater information.
 
J

Jack Saalweachter

Martin said:
I didn't understood how [3][5] got converted to Square *, since it only
takes an unsigned const as argument?

This trick goes back a way to a standard programming trick they teach in
schools (which isn't actually useful, but kind of clever).

If you tell students in their first C programming class, "dynamically
create a 2-d array, with size NxM", they'll do one of the following:

char **array = malloc(N * sizeof(char*));
for (int i = 0; i < N; ++i) {
array = malloc(M * sizeof(char));
}

and then access the array by saying 'array[j]'.

or

char *array = malloc(N * M * sizeof(char));

and then access the array by saying 'array[i*M + j]'.


In their second-year programming class, the students learn a trick, and
they'll write the following:

char *flatArray = malloc(N * M * sizeof(char));
char **array = malloc(N * sizeof(char*));
for (int i = 0; i < N; ++i) {
array = flatArray + i*M;
}

and then access the array by saying 'array[j]'.

This combines the advantages of the first two ways: you have the easy
access of the first way ('array[j]'), but when it comes time to clean
up, you only have to free the two arrays (flatArray and array). In the
first way, you had to remember to free all of the rows of the array; if
you just say 'free(array)', you leak them.

(The third year student would skip the extra variable 'flatArray', and
just allocate the space in array[0]; the fourth year student would say,
"Why the hell do I need a 2d array?")


This C++ trick is really just the equivalent of the second-year C
student's trick. However, instead of storing the second char** array
internally, it creates entries from it whenever you ask for them.


Jack Saalweachter
 
?

=?ISO-8859-1?Q?Martin_J=F8rgensen?=

Jack said:
Martin Jørgensen wrote: -snip-

This C++ trick is really just the equivalent of the second-year C
student's trick. However, instead of storing the second char** array
internally, it creates entries from it whenever you ask for them.

You didn't really answer my questions. I think location 20 would be
[3][4] since that looks like *(squares + 8 * 2)[4], but I just wanted to
be sure.


Best regards
Martin Jørgensen
 
J

Jack Saalweachter

Martin said:
You didn't really answer my questions. I think location 20 would be
[3][4] since that looks like *(squares + 8 * 2)[4], but I just wanted to
be sure.
You are correct: I completely misunderstood what you were asking.

Deep down, it shouldn't matter how you access location 20; what gets
mapped to location 20 is effectively 'implementation defined'. The goal
of the chessboard class is to abstract away the array and just give you
array-like access; if you can say 'board[3][4]' to refer to a location
on the board, why would you ever want to try to refer to location 20 in
the underlying array?

Just to be mean, the chessboard class could implement its op[] like so:

Square *operator[](unsigned const x)
{
return squares + 8 * (8 - x - 1);
}

From the outside, the chessboard behaves the same; however, location 20
is no longer mapped to board[3][4].


Jack Saalweachter
 
T

Tomás

Martin Jørgensen posted:

board[3][5] = ChessBoard::Square::bishop;

I didn't understood how [3][5] got converted to Square *, since it only
takes an unsigned const as argument?


Analyse the following statement:


board[3][5] = ChessBoard::Square::bishop;


If you look up an operator precedence table, you'll see that it's
identical to:


( board[3] )[5] = ChessBoard::Square::bishop;


It's plain to see that operator[] is applied to "board". If we look at
the definition of this member function, we see that it returns a Square*,
i.e. a pointer. So it becomes:

(p)[5] = ChessBoard::Square::bishop;


When you apply [] to an intrinsic type, it evaluates to:


*(p + 5) = ChessBoard::Square::bishop;


So, looking at it again from the start:

board[3] evaluates to a pointer to a Square.
The block brackets are then applied to the pointer in order to access
an element at a particular index relative to the original pointer value.


-Tomás
 

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

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top