Typed arrays (was: Is it ANSI or is it compiler dependent?)

C

Canonical Latin

Leor Zolman said:
Canonical Latin said:
...
But I'm still curious as to the rational of having type
pointer-to-array-of-size-N-of-type-T (which is fine) and not having type
array-of-size-N-of-type-T (with some exceptions, which is curious).
So far
the consensus seems to be that while everyone is aware of this no one knows
the rational behind it. I'm sure there must be a compelling reason for this
and once explained it would be obvious :)
I know you asked me not to answer any more of your questions, but I'm
stubborn ;-)

The place where you can't have array-of-size-N is in the special case of a
function parameter. Otherwise they're fine.

In the case of function parameters, if an array got passed by value, that
would be a very expensive operation. Most of the time folks would have to
program around it, by writing an expression that evaluates to a pointer of
some kind and passing /that/. But believe it or not, if arrays got passed
by value, that would actually be the special case...because of the
following:

As I'm fond of saying, arrays are just "smoke and mirrors" anyway; they're
mostly syntactic sugar, and compilers translate array names into pointers
to their first elements (there are a few exceptions, such as when applying
sizeof). That's true even if you don't pass them to a function.

So, given:
int a[10];
a[3] = 5;

that last line actually compiles into something like:
*(&a[0] + 3) = 5;

Therefore, in a function call such as:
func(a);

it makes perfect sense what gets passed is a pointer to the first element
of the array:

func(&a[0]);

and thus there's no information available to the function about the size of
that array.
-leor

--
Leor Zolman --- BD Software --- www.bdsoft.com
On-Site Training in C/C++, Java, Perl and Unix
C++ users: download BD Software's free STL Error Message Decryptor at:
www.bdsoft.com/tools/stlfilt.html

Type checking and passing by value are two different issues. Consider:

fun1 (T[]); // pointer-to-type-T

fun2 (T[2]); // array-of-size-2-of-type-T

fun3 (T[3]); // array-of-size-3-of-type-T

fun4 (T**); // pointer-to-pointer-to-type-T

fun5 (T*[2]); // pointer-to-array-of-size-2-of-type-T

fun6 (T*[3]); // pointer-to-array-of-size-3-of-type-T



In none of the cases the whole array is passed by value and this is not the
issue. Suppose that c++ actually did have a legitimate type
array-of-size-N-of-type-T. Then

fun (T x[]); // fun1

fun (T x[2]); // fun2

fun (T x[3]); // fun3

main() {

T a[2];

T b[3];

T c[4];

T *d;

fun(a); // call fun2

fun(b) ; // call fun3

fun(c) ; // call fun1 (see comment at end)

fun(d) ; // call fun1

}

Obviously this doesn't work in c++ because compiler sees
array-of-size-N-of-type-T as pointer-to-type-T in most cases. Enforcing type
checking for arrays has nothing to do with passing the whole array by value.
If you think my description of supposed behavior is too far fetched, or that
arrays are just "smoke and mirror" then consider

void fun (T **x) { cout << "fun1"; }

void fun (T (*x)[2]) { cout << "fun2"; }

void fun (T (*x)[3]) { cout << "fun3"; }

int main(int argc, char *argv[]) {

T (*a)[2];

T (*b)[3];

T (*c)[4];

T **d;

fun(a); // call fun2

fun(b) ; // call fun3

fun((T**)(c)) ; // call fun1 (unsafe in c++)

fun(d) ; // call fun1

}

which compiles and behaves as expected. Note that nothing is passed by
value. What this is saying is that pointer-to-array-of-size-N-of-type-T is a
different type for each value of N. What I am saying is that so far I have
not heard a compelling reason why array-of-size-N-of-type-T should not also
be a different type for each value of N.



Another issue that is closely related to this: There is already a problem in
c++ with type pointer-to-array-of-size-N-of-type-T which I marked as
"unsafe" in my example. Essentially you cannot convert
pointer-to-array-of-size-N-of-type-T to pointer-to-pointer-of-type-T without
using a reinterpret cast. This perhaps is the source of the odd behavior of
reinterpreting array-of-size-N-of-type-T as pointer-to-type-T. Of course,
this whole business would go away (and we get true typed arrays in bargain)
if array-of-size-N-of-type-T is automatically converted to pointer-to-type-T
only if there is no matching type for the specific value of N.
 
C

Canonical Latin

Andrey Tarasevich said:
Leor said:
In the case of function parameters, if an array got passed by value, that
would be a very expensive operation. Most of the time folks would have to
program around it, by writing an expression that evaluates to a pointer of
some kind and passing /that/. But believe it or not, if arrays got passed
by value, that would actually be the special case...

That's not really true. Conceptually, arrays are aggregates and in this
respect they are not different from structs. Making arrays passable "by
value" wouldn't make things more complicated or inconvenient they
already are with structs. Folks would have to do the same thing they
always did - if you want to pass something large and don't need a copy -
pass it "by pointer".

Actually, impossibility to pass arrays by value made things _more_
cumbersome and error prone, because it breaks the invariance of the code
with respect to aggregate types hidden behind typedef-names. The
following function will accepts its parameters very differently,
depending on whether typedef-name 'T' designates array type or struct type

void foo(T agg)

and that's not a good thing.
because of the following:

As I'm fond of saying, arrays are just "smoke and mirrors" anyway; they're
mostly syntactic sugar, and compilers translate array names into pointers
to their first elements (there are a few exceptions, such as when applying
sizeof).

The only reason you can call these contexts (where arrays don't decay
into pointers) "exceptions" is that these contexts are apparently
relatively rare compared to the contexts where arrays do decay to
pointers (especially true for C). But that's a fake reason. Nothing more
than an illusion.

From the formal point of view, arrays keeping their "arrayness" is the
_normal_ behavior of arrays, while arrays decaying into pointers is the
abnormal (exceptional) behavior. It is definitely not correct to say
that arrays are just "smoke and mirrors".
That's true even if you don't pass them to a function.

So, given:
int a[10];
a[3] = 5;

that last line actually compiles into something like:
*(&a[0] + 3) = 5;

Therefore, in a function call such as:
func(a);

it makes perfect sense what gets passed is a pointer to the first element
of the array:

func(&a[0]);

and thus there's no information available to the function about the size of
that array.

Well, it is also important to understand that allowing an array to decay
to a pointer when passed to a function is a useful trick, which has its
own specific purpose. It purpose is to help create functions that can
work with arrays of different sizes.

In contexts where the ability to work with differently sized arrays is
not needed, it makes more sense to pass arrays by array-typed
pointer/reference (i.e. by a pointer/reference of
pointer/reference-to-array type). For example, an application that works
with, say, 3d geometry and used the following type to represent points
in 3D space

typedef int point_t[3];

should pass these points to functions as follows

void foo(const point_t& point)

or

void bar(const int (&point)[3])

not as

void foo(const point_t point)
void bar(const int point[3])

Note, that such function declaration are invariant to the possible
future change of 'point_t' definition to

typedef struct point_t { int x, y, z; };

I like your reply Andery. But your idea is a bit more revolutionary than
mine. Read my alternative solution of decaying array-to-size-N-of-type-T to
pointer-to-type-T only if there is no matching type for N in the new thread
"Typed Arrays". I won't shy away from some constructive criticism :)
 
A

Andrey Tarasevich

Canonical said:
...
Type checking and passing by value are two different issues. Consider:

fun1 (T[]); // pointer-to-type-T

fun2 (T[2]); // array-of-size-2-of-type-T

fun3 (T[3]); // array-of-size-3-of-type-T

fun4 (T**); // pointer-to-pointer-to-type-T

fun5 (T*[2]); // pointer-to-array-of-size-2-of-type-T

No, it is an array of 2 'T*'s.
fun6 (T*[3]); // pointer-to-array-of-size-3-of-type-T

No, it is an array of 3 'T*'s.

You probably meant

fun5(T(*)[2])
fun5(T(*)[3])
In none of the cases the whole array is passed by value and this is not the
issue. Suppose that c++ actually did have a legitimate type
array-of-size-N-of-type-T. Then

But, C++ does have a legitimate type array-of-size-N-of-type-T. Not
fully functional though.
fun (T x[]); // fun1

fun (T x[2]); // fun2

fun (T x[3]); // fun3

main() {

T a[2];

T b[3];

T c[4];

T *d;

fun(a); // call fun2

fun(b) ; // call fun3

fun(c) ; // call fun1 (see comment at end)

fun(d) ; // call fun1

}

Obviously this doesn't work in c++ because compiler sees
array-of-size-N-of-type-T as pointer-to-type-T in most cases.

It is not exactly correct. Compiler sees array-of-size-N-of-type-T as
pointer-to-type-T in case of function parameter declaration. The rest
follows. This is specific to situations when one makes syntactical
attempt to pass arrays "by value".

Note, that if you declare your functions as

fun (T (&x)[2]); // fun2
fun (T (&x)[3]); // fun3

the overloading will start working as you describe.
Enforcing type
checking for arrays has nothing to do with passing the whole array by value.
If you think my description of supposed behavior is too far fetched, or that
arrays are just "smoke and mirror" then consider

void fun (T **x) { cout << "fun1"; }

void fun (T (*x)[2]) { cout << "fun2"; }

void fun (T (*x)[3]) { cout << "fun3"; }

int main(int argc, char *argv[]) {

T (*a)[2];

T (*b)[3];

T (*c)[4];

T **d;

fun(a); // call fun2

fun(b) ; // call fun3

fun((T**)(c)) ; // call fun1 (unsafe in c++)

fun(d) ; // call fun1

}

which compiles and behaves as expected. Note that nothing is passed by
value. What this is saying is that pointer-to-array-of-size-N-of-type-T is a
different type for each value of N. What I am saying is that so far I have
not heard a compelling reason why array-of-size-N-of-type-T should not also
be a different type for each value of N.

It _is_ a different type for each value of on. In certain contexts this
difference is lost because of array-to-pointer conversion.
Another issue that is closely related to this: There is already a problem in
c++ with type pointer-to-array-of-size-N-of-type-T which I marked as
"unsafe" in my example. Essentially you cannot convert
pointer-to-array-of-size-N-of-type-T to pointer-to-pointer-of-type-T without
using a reinterpret cast. This perhaps is the source of the odd behavior of
reinterpreting array-of-size-N-of-type-T as pointer-to-type-T. Of course,
this whole business would go away (and we get true typed arrays in bargain)
if array-of-size-N-of-type-T is automatically converted to pointer-to-type-T
only if there is no matching type for the specific value of N.

Hmm.. I don't exactly understand what you are trying to say here.
 
C

Canonical Latin

Andrey Tarasevich said:
Canonical said:
...
Type checking and passing by value are two different issues. Consider:

fun1 (T[]); // pointer-to-type-T
fun2 (T[2]); // array-of-size-2-of-type-T
fun3 (T[3]); // array-of-size-3-of-type-T
fun4 (T**); // pointer-to-pointer-to-type-T
fun5 (T*[2]); // pointer-to-array-of-size-2-of-type-T

No, it is an array of 2 'T*'s.
fun6 (T*[3]); // pointer-to-array-of-size-3-of-type-T

No, it is an array of 3 'T*'s.

You probably meant

fun5(T(*)[2])
fun5(T(*)[3])

Oops! Yes I did mean that
In none of the cases the whole array is passed by value and this is not the
issue. Suppose that c++ actually did have a legitimate type
array-of-size-N-of-type-T. Then

But, C++ does have a legitimate type array-of-size-N-of-type-T. Not
fully functional though.
fun (T x[]); // fun1
fun (T x[2]); // fun2
fun (T x[3]); // fun3
main() {
T a[2];
T b[3];
T c[4];

T *d;
fun(a); // call fun2
fun(b) ; // call fun3
fun(c) ; // call fun1 (see comment at end)
fun(d) ; // call fun1
}

Obviously this doesn't work in c++ because compiler sees
array-of-size-N-of-type-T as pointer-to-type-T in most cases.

It is not exactly correct. Compiler sees array-of-size-N-of-type-T as
pointer-to-type-T in case of function parameter declaration. The rest
follows. This is specific to situations when one makes syntactical
attempt to pass arrays "by value".

Note, that if you declare your functions as

fun (T (&x)[2]); // fun2
fun (T (&x)[3]); // fun3
That is great. It is almost there. The only problem is that now you cannot
have
fun (T x[]); // fun1
and call it with
T xx[4];
fun(xx);
the overloading will start working as you describe.
Enforcing type
checking for arrays has nothing to do with passing the whole array by value.
If you think my description of supposed behavior is too far fetched, or that
arrays are just "smoke and mirror" then consider

void fun (T **x) { cout << "fun1"; }
void fun (T (*x)[2]) { cout << "fun2"; }
void fun (T (*x)[3]) { cout << "fun3"; }
int main(int argc, char *argv[]) {
T (*a)[2];
T (*b)[3];
T (*c)[4];
T **d;
fun(a); // call fun2
fun(b) ; // call fun3
fun((T**)(c)) ; // call fun1 (unsafe in c++)
fun(d) ; // call fun1
}

which compiles and behaves as expected. Note that nothing is passed by
value. What this is saying is that pointer-to-array-of-size-N-of-type-T is a
different type for each value of N. What I am saying is that so far I have
not heard a compelling reason why array-of-size-N-of-type-T should not also
be a different type for each value of N.

It _is_ a different type for each value of on. In certain contexts this
difference is lost because of array-to-pointer conversion.
Another issue that is closely related to this: There is already a problem in
c++ with type pointer-to-array-of-size-N-of-type-T which I marked as
"unsafe" in my example. Essentially you cannot convert
pointer-to-array-of-size-N-of-type-T to pointer-to-pointer-of-type-T without
using a reinterpret cast. This perhaps is the source of the odd behavior of
reinterpreting array-of-size-N-of-type-T as pointer-to-type-T. Of course,
this whole business would go away (and we get true typed arrays in bargain)
if array-of-size-N-of-type-T is automatically converted to pointer-to-type-T
only if there is no matching type for the specific value of N.

Hmm.. I don't exactly understand what you are trying to say here.
I'm not sure I do either, but I try! Here is another attempt: as it is,
array-of-size-N-of-type-T decays to pointer-of-type-T when not argument to
sizeof and as you noted not qualified by &. Lets say I want array type
checking and still want to be able to pass T[N] to a function that does not
specify the N value e.g.
fun(T *x); // funA
fun(T (&x)[2]); // funB
main() {
T x[3] ;
fun(x) ; // funA
}
So the compiler tries to find fun(T (&x)[3]) and since it can't then it
automatically looks for fun(T *x); // funA
 
L

Leor Zolman

Type checking and passing by value are two different issues. Consider:

fun1 (T[]); // pointer-to-type-T
yes.


fun2 (T[2]); // array-of-size-2-of-type-T

Now, are you showing what you'd like it to be, or what you think is
actually being declared? As per all that's been said before, the type of
the parameter above is actually the same as the previous: pointer-to-T.
fun3 (T[3]); // array-of-size-3-of-type-T

again, just pointer-to-T.
fun4 (T**); // pointer-to-pointer-to-type-T
yes.


fun5 (T*[2]); // pointer-to-array-of-size-2-of-type-T

Nope, just pointer-to-pointer-to-T.
fun6 (T*[3]); // pointer-to-array-of-size-3-of-type-T

Still just pointer-to-pointer-to-T.
In none of the cases the whole array is passed by value and this is not the
issue. Suppose that c++ actually did have a legitimate type
array-of-size-N-of-type-T. Then

fun (T x[]); // fun1

fun (T x[2]); // fun2

fun (T x[3]); // fun3

main() {

T a[2];

T b[3];

T c[4];

T *d;

fun(a); // call fun2

fun(b) ; // call fun3

fun(c) ; // call fun1 (see comment at end)

fun(d) ; // call fun1

/IF/ C++ did as you'd like, then it would break all pre-existing code that
relies on the age-old behavior of arrays "decaying" to pointers. Imagine
requiring there to be an overload for each possible length N in cases such
as:

char string[N];
...
int i = strlen(string);

Would we want to have to have a separately compiled version of strlen for
each possible value of N? Or for strlen to be a template with a non-type
template parameter N? Yuck.
}

Obviously this doesn't work in c++ because compiler sees
array-of-size-N-of-type-T as pointer-to-type-T in most cases.

Remember that arrays retain their full type information as long as the
name they were declared with (as arrays) is still in scope and you use it.
That's why sizeof(array) works and &array actually yields a
pointer-to-array. But most of the things you do with arrays, such as
subscripting, end up with you losing that "array-ness" because it is no
longer needed by the resulting expression type. The fact this information
is also lost when using an array as a function argument is just an
occupational hazard of C/C++ programming. Check out today's thread with
subject "Copying struct with array" for some history as to why structure
assignment is allowed while array assignment is not; it's quite related to
this issue.
Enforcing type
checking for arrays has nothing to do with passing the whole array by value.
If you think my description of supposed behavior is too far fetched, or that
arrays are just "smoke and mirror" then consider

It's not that I just /think/ they're smoke and mirrors, I know from having
implemented a C compiler by hand that native C/C++ arrays are essentially
nothing more than notational convenience (and extremely so in the case of
array function parameters). If you want object semantics for something
that works like an array, simply use a vector, or even boost::array if you
don't require dynamic resizing.

Every so often folks suggest how things "should" be in C or C++, and it
isn't that they aren't good ideas, but they're not good enough for the
chaos attempting to retro-fit them to the language /now/ would inevitably
cause. C++ is difficult enough to grok as it stands. I think the entire C++
community is only barely beginning to feel a bit of relief that compilers
in general have /almost/ caught up with the language as it was standardized
in 1998 (and only one family, the one based on the EDG front end, actually
has). Changes to the basic semantics of arrays just aren't going to happen.
void fun (T **x) { cout << "fun1"; }

void fun (T (*x)[2]) { cout << "fun2"; }

void fun (T (*x)[3]) { cout << "fun3"; }

int main(int argc, char *argv[]) {

T (*a)[2];

T (*b)[3];

T (*c)[4];

T **d;

fun(a); // call fun2

fun(b) ; // call fun3

fun((T**)(c)) ; // call fun1 (unsafe in c++)

fun(d) ; // call fun1

}

which compiles and behaves as expected. Note that nothing is passed by
value. What this is saying is that pointer-to-array-of-size-N-of-type-T is a
different type for each value of N. What I am saying is that so far I have
not heard a compelling reason why array-of-size-N-of-type-T should not also
be a different type for each value of N.

As I said above, even if this would be an overall improvement to the
language (and I'm not saying it would be; I haven't completely thought it
through, and I'm not likely to), it would no longer be C or C++ and thus
could not possibly be worth breaking most of the existing C/C++ code base
to effect.

Note that using boost::array, you can achieve the overloading you want,
retain full performance of native arrays, and still "pass" arrays as
efficiently as C/C++ doesn't (just pass by reference).
Another issue that is closely related to this: There is already a problem in
c++ with type pointer-to-array-of-size-N-of-type-T which I marked as
"unsafe" in my example. Essentially you cannot convert
pointer-to-array-of-size-N-of-type-T to pointer-to-pointer-of-type-T without
using a reinterpret cast. This perhaps is the source of the odd behavior of
reinterpreting array-of-size-N-of-type-T as pointer-to-type-T. Of course,
this whole business would go away (and we get true typed arrays in bargain)
if array-of-size-N-of-type-T is automatically converted to pointer-to-type-T
only if there is no matching type for the specific value of N.

Passing multi-dimensional arrays around takes some intestinal fortitude. It
always confuses the hell out of /me/, so I can't disagree about the
languages being weak in that regard. To make matters worse, there are no
wonderful replacements for multidimensional arrays in the modern C++ libs,
alas (at least none I've found yet.)
-leor
 
C

Canonical Latin

Leor Zolman said:
fun4 (T**); // pointer-to-pointer-to-type-T yes.
fun5 (T*[2]); // pointer-to-array-of-size-2-of-type-T
Nope, just pointer-to-pointer-to-T.
fun6 (T*[3]); // pointer-to-array-of-size-3-of-type-T
Still just pointer-to-pointer-to-T.
Granted that I made a mistake in my notation here as Andrey pointed out. The
correct way to say this is
fun (T**); // pointer-to-pointer-to-type-T
fun (T(*)[2]); // pointer-to-array-of-size-2-of-type-T
fun (T(*)[3]); // pointer-to-array-of-size-3-of-type-T
I may be mistaken but from reading your post it seems to me that you are
under the impression that the 3 functions can't be overloaded as above. But
they can. There is a real honest to goodness (lol) type
pointer-to-array-of-size-3-of-type-T that does not decay to anything.
...
void fun (T **x) { cout << "fun1"; }
void fun (T (*x)[2]) { cout << "fun2"; }
void fun (T (*x)[3]) { cout << "fun3"; }
int main() {
T (*a)[2];
T (*b)[3];
T (*c)[4];
T **d;
fun(a); // call fun2
fun(b) ; // call fun3
fun((T**)(c)) ; // call fun1 (unsafe)
fun(d) ; // call fun1
}
...
As I said above, even if this would be an overall improvement to the
language (and I'm not saying it would be; I haven't completely thought it
through, and I'm not likely to), it would no longer be C or C++ and thus
could not possibly be worth breaking most of the existing C/C++ code base
to effect.

Try compiling the code that I gave above. It really does compile and run
with obvious modifications (e.g. if replace T with say 'int'). Also Andrey
answered my question in a very surprising way. You can coerce the compiler
to do array type checking for you. I just don't see why you have to jump
through hoops to do it. But the following code does exactly what I thought
can't be done--but i'm learning :)

#include<iostream>
void fun(int *&x) {std::cout << "fun1" << std::endl;}
void fun(int (&x)[2]) {std::cout << "fun2" << std::endl;}
void fun(int (&x)[3]) {std::cout << "fun3" << std::endl;}
int main() {
int aa[2];
int bb[3];
int cc[4];
fun(aa); // fun2
fun(bb); // fun3
int *p_cc=cc; // manual decay
fun(p_cc); // fun1
}

Alas you need that "manual decay" :( But on the bright side I qualified endl
with std namespace :)
 
T

tom_usenet

It is not exactly correct. Compiler sees array-of-size-N-of-type-T as
pointer-to-type-T in case of function parameter declaration. The rest
follows. This is specific to situations when one makes syntactical
attempt to pass arrays "by value".

Note, that if you declare your functions as

fun (T (&x)[2]); // fun2
fun (T (&x)[3]); // fun3
That is great. It is almost there. The only problem is that now you cannot
have
fun (T x[]); // fun1
and call it with
T xx[4];
fun(xx);

Yes you can. You can have overloads for array ones and pointer ones.
Hmm.. I don't exactly understand what you are trying to say here.
I'm not sure I do either, but I try! Here is another attempt: as it is,
array-of-size-N-of-type-T decays to pointer-of-type-T when not argument to
sizeof and as you noted not qualified by &. Lets say I want array type
checking and still want to be able to pass T[N] to a function that does not
specify the N value e.g.
fun(T *x); // funA
fun(T (&x)[2]); // funB
main() {
T x[3] ;
fun(x) ; // funA
}
So the compiler tries to find fun(T (&x)[3]) and since it can't then it
automatically looks for fun(T *x); // funA

Yes that will work (once you add the int returns back in - implicit
int return is an old C feature, never a C++ one, although some
compilers have supported it for backwards compatibility reasons).

For the ultimate:

template <int N>
void fun(T (&x)[N])
{
//N gives the size of the array!
}

Tom
 
L

Leor Zolman

Leor Zolman said:
fun4 (T**); // pointer-to-pointer-to-type-T yes.
fun5 (T*[2]); // pointer-to-array-of-size-2-of-type-T
Nope, just pointer-to-pointer-to-T.
fun6 (T*[3]); // pointer-to-array-of-size-3-of-type-T
Still just pointer-to-pointer-to-T.
Granted that I made a mistake in my notation here as Andrey pointed out. The
correct way to say this is
fun (T**); // pointer-to-pointer-to-type-T
fun (T(*)[2]); // pointer-to-array-of-size-2-of-type-T
fun (T(*)[3]); // pointer-to-array-of-size-3-of-type-T

And this post was written before Andrey's post came up; it was just delayed
for hours due to a news server glitch at my end, and I eventually re-sent
it.
I may be mistaken but from reading your post it seems to me that you are
under the impression that the 3 functions can't be overloaded as above.

I was, and still am, if you're talking about the exact way you posted them
above (as opposed to your corrected version down below). Try compiling this
(and if it works, please tell me what compiler you're using):

#include <iostream>
using namespace std;

typedef int T;

void fun (T x[]) {cout << "fun(T x[])" << endl; }
void fun (T x[2]) {cout << "fun(T x[2])" << endl; }
void fun (T x[3]) {cout << "fun(T x[3])" << endl; }

int main() {
T a[2];
T b[3];
T c[4];
T *d;

fun(a); // call fun2
fun(b) ; // call fun3
fun(c) ; // call fun1 (see comment at end)
fun(d) ; // call fun1

return 0;
}

But
they can. There is a real honest to goodness (lol) type
pointer-to-array-of-size-3-of-type-T that does not decay to anything.
...
void fun (T **x) { cout << "fun1"; }
void fun (T (*x)[2]) { cout << "fun2"; }
void fun (T (*x)[3]) { cout << "fun3"; }
int main() {
T (*a)[2];
T (*b)[3];
T (*c)[4];
T **d;
fun(a); // call fun2
fun(b) ; // call fun3
fun((T**)(c)) ; // call fun1 (unsafe)
fun(d) ; // call fun1
}
...
As I said above, even if this would be an overall improvement to the
language (and I'm not saying it would be; I haven't completely thought it
through, and I'm not likely to), it would no longer be C or C++ and thus
could not possibly be worth breaking most of the existing C/C++ code base
to effect.

Try compiling the code that I gave above. It really does compile and run
with obvious modifications (e.g. if replace T with say 'int').

Yes, I know; by "this" I meant having it allow overloads such as in the
example code I just gave you above (which doesn't compile for me.)

Also Andrey
answered my question in a very surprising way. You can coerce the compiler
to do array type checking for you. I just don't see why you have to jump
through hoops to do it. But the following code does exactly what I thought
can't be done--but i'm learning :)

#include<iostream>
void fun(int *&x) {std::cout << "fun1" << std::endl;}
void fun(int (&x)[2]) {std::cout << "fun2" << std::endl;}
void fun(int (&x)[3]) {std::cout << "fun3" << std::endl;}
int main() {
int aa[2];
int bb[3];
int cc[4];
fun(aa); // fun2
fun(bb); // fun3
int *p_cc=cc; // manual decay
fun(p_cc); // fun1
}

Yup, there are certainly ways to do what you're trying to do. The issue I
thought I was discussing was the suggestion that C++ should work
differently than it does when it comes to array names appearing as function
arguments, and the decay that entails. As long as you're not still wanting
that to happen, I've no qualms with any of the rest!
Alas you need that "manual decay" :( But on the bright side I qualified endl
with std namespace :)

:)
-leor
 
C

Canonical Latin

tom_usenet said:

I appreciate the pointers Tom, no pun intended. Just to summarize: My
question was "what is the rational of c++ not having typed arrays in
function calls" and your reply was "But it does if you are careful with
syntax such as:"
template <class T, int N>
void fun(T (&x)[N]) {
std::cout << N << std::endl;
}

Let's go back to function overloading. Surprisingly
int fun (int *x) {} //fun1
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
fun(x); // ambiguous
}
Does not compile (gcc 3.2.3). It complains about function call being
ambiguous. I don't understand why it is behaving this way, but I humor it
and fix the problem as
int fun (int *&x) {} //fun3
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
int y[3];
fun(x); // fun2
fun(y); // ambiguos
}
Now the compiler knows how to resolve fun(x) call, but gets stuck on fun(y)
being ambiguous. So I say "huh!" and try various casts on y in fun(y). But
uh-uh it ain't having none of that. So I sit and reason with it and say, hay
look
int fun (int *&x) {} //fun3
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
int y[3];
fun(x); // fun2
int *p = y;
fun(p); // fun3
}
Now it knows what I'm saying and grudgingly compiles my measly code. But
now, that new variable is a thorn on my side -- if I were looking at someone
else's code I would simply "clean it up". So after my long winded monolog I
come to the question: Suppose I want array type checking and also want
arrays that don't have a matching type to a function to be passed to a
default function (a la example above). Is there a clean way to do this?
 
B

Buster

Canonical said:
So after my long winded monolog I
come to the question: Suppose I want array type checking and also want
arrays that don't have a matching type to a function to be passed to a
default function (a la example above). Is there a clean way to do this?

#include <ostream>
#include <iostream>
#include <string>

template <typename T, unsigned N>
void fun (T (& aref) [N])
{
std::cout << "default code\n";
}

template <typename T>
void fun (T (& aref) [3])
{
std::cout << "code for arrays of length 3\n";
}

template <unsigned N>
void fun (double (& aref) [N])
{
std::cout << "code for arrays of double\n";
}

void fun (double (& aref) [3])
{
std::cout << "resolve the ambiguity\n";
}

void test_drive ()
{
double a1 [4];
double a2 [3];
int a3 [4];
std::string a4 [3];

fun (a1);
fun (a2);
fun (a3);
fun (a4);
}
 
C

Canonical Latin

Buster said:
Canonical said:
Suppose I want array type checking and also want
arrays that don't have a matching type to a function to be passed to a
default function (a la example above). Is there a clean way to do this?

#include <ostream>
#include <iostream>
#include <string>

template <typename T, unsigned N>
void fun (T (& aref) [N])
{
std::cout << "default code\n";
}

template <typename T>
void fun (T (& aref) [3])
{
std::cout << "code for arrays of length 3\n";
}

template <unsigned N>
void fun (double (& aref) [N])
{
std::cout << "code for arrays of double\n";
}

void fun (double (& aref) [3])
{
std::cout << "resolve the ambiguity\n";
}

void test_drive ()
{
double a1 [4];
double a2 [3];
int a3 [4];
std::string a4 [3];

fun (a1);
fun (a2);
fun (a3);
fun (a4);
}

Your solution does work for what I'd asked and I thank you for it. Template
specialization is an interesting solution. However, if there is a solution
without using template for the default function I'm still interested (the
function being default there is no reason to generate identical object code
for each value of N, but that's just me).
 
A

Andrey Tarasevich

Canonical said:
question was "what is the rational of c++ not having typed arrays in
function calls" and your reply was "But it does if you are careful with
syntax such as:"
template <class T, int N>
void fun(T (&x)[N]) {
std::cout << N << std::endl;
}

Let's go back to function overloading. Surprisingly
int fun (int *x) {} //fun1
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
fun(x); // ambiguous
}
Does not compile (gcc 3.2.3). It complains about function call being
ambiguous. I don't understand why it is behaving this way,

It is ambiguous because in C++ the array-to-pointer conversion (required
for the first function) and no-conversion (second function) have the
same rank in the overload resolution process - 'Exact match' (see
13.3.3.1.1/3). In other words, from the compiler's point of view both
functions look equally good in this case.
but I humor it
and fix the problem as
int fun (int *&x) {} //fun3
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
int y[3];
fun(x); // fun2
fun(y); // ambiguos
}
Now the compiler knows how to resolve fun(x) call, but gets stuck on fun(y)
being ambiguous.

Ambiguous? I don't think it is ambiguous. It is simply impossible.
There's no candidate 'fun' function for 'fun(y)' call. The first
function cannot be called because it requires an lvalue as its argument,
and the result of the array-to-pointer conversion is an rvalue. The
second function simply doesn't match at all.

That's also the reason why the compiler no longer complains about
ambiguity in the 'fun(x)' call. The first function can no longer be used
with arrays.

You can modify the first function declaration to

int fun (int* const& x)

an the second call will compile (resolve to the first function). But the
first will become ambiguous again.
So I say "huh!" and try various casts on y in fun(y). But
uh-uh it ain't having none of that. So I sit and reason with it and say, hay
look
int fun (int *&x) {} //fun3
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
int y[3];
fun(x); // fun2
int *p = y;
fun(p); // fun3
}
Now it knows what I'm saying and grudgingly compiles my measly code.

Now you have an lvalue ('p'), which the first function can be called with.
But
now, that new variable is a thorn on my side -- if I were looking at someone
else's code I would simply "clean it up". So after my long winded monolog I
come to the question: Suppose I want array type checking and also want
arrays that don't have a matching type to a function to be passed to a
default function (a la example above). Is there a clean way to do this?

Well, the fact that in overload resolution the array-to-pointer
conversion is given the same rank as no-conversion is one of those many
things that turn arrays into second-class citizens in C++...
 
B

Buster

Canonical said:
However, if there is a solution
without using template for the default function I'm still interested (the
function being default there is no reason to generate identical object code
for each value of N, but that's just me).

template <typename T>
void fun_helper (T * p)
{
std::cout << "default (one per type)\n";
}

template <typename T, unsigned N>
inline void fun (T (& aref) [N])
{
std::cout << "forwarding\n";
fun_helper (aref);
}
 
C

Canonical Latin

Buster said:
Canonical said:
However, if there is a solution
without using template for the default function I'm still interested (the
function being default there is no reason to generate identical object code
for each value of N, but that's just me).

template <typename T>
void fun_helper (T * p)
{
std::cout << "default (one per type)\n";
}

template <typename T, unsigned N>
inline void fun (T (& aref) [N])
{
std::cout << "forwarding\n";
fun_helper (aref);
}

How I wish that would worke. But "surprisingly"
int fun (int *x) {} //fun1
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
fun(x); // ambiguous
}
Does not compile (gcc 3.2.3). As Andrey says
"Well, the fact that in overload resolution the array-to-pointer
conversion is given the same rank as no-conversion is one of those many
things that turn arrays into second-class citizens in C++..."
 
C

Canonical Latin

Andrey Tarasevich said:
Canonical Latin wrote:
...
int fun (int *&x) {} //fun3
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
int y[3];
fun(x); // fun2
fun(y); // ambiguos
}
Now the compiler knows how to resolve fun(x) call, but gets stuck on fun(y)
being ambiguous.

Ambiguous? I don't think it is ambiguous. It is simply impossible.
There's no candidate 'fun' function for 'fun(y)' call. The first
function cannot be called because it requires an lvalue as its argument,
and the result of the array-to-pointer conversion is an rvalue. The
second function simply doesn't match at all.

Yes I see it now.
...

Well, the fact that in overload resolution the array-to-pointer
conversion is given the same rank as no-conversion is one of those many
things that turn arrays into second-class citizens in C++...

Thank you. I did learn quite a bit.
 
B

Buster

Canonical said:
Buster said:
template <typename T>
void fun_helper (T * p)
{
std::cout << "default (one per type)\n";
}

template <typename T, unsigned N>
inline void fun (T (& aref) [N])
{
std::cout << "forwarding\n";
fun_helper (aref);
}


How I wish that would worke. But "surprisingly"
int fun (int *x) {} //fun1
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
fun(x); // ambiguous
}

That's different from what I wrote, which works fine.
 
C

Canonical Latin

Buster said:
Canonical said:
Buster said:
template <typename T>
void fun_helper (T * p)
{
std::cout << "default (one per type)\n";
}

template <typename T, unsigned N>
inline void fun (T (& aref) [N])
{
std::cout << "forwarding\n";
fun_helper (aref);
}


How I wish that would worke. But "surprisingly"
int fun (int *x) {} //fun1
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
fun(x); // ambiguous
}

That's different from what I wrote, which works fine.

Yes I completely missed it. I really like your solution. Thank you.
 
S

Sergiy Kanilo

How I wish that would worke. But "surprisingly"
int fun (int *x) {} //fun1
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
fun(x); // ambiguous
}

class int_p
{
public:
int_p(int*ptr):ptr_(ptr){}
operator int*()const{ return ptr_; }
private:
int* ptr_;
};

void fun (int_p x) {} //fun1
void fun (int (&x)[2]){} //fun2

int main() {
int x[2];
int y[3];
fun(x); // calls fun2
fun(y); // calls fun1
}

Cheers,
Serge
 
C

Canonical Latin

Sergiy Kanilo said:
How I wish that would worke. But "surprisingly"
int fun (int *x) {} //fun1
int fun (int (&x)[2]){} //fun2
int main() {
int x[2];
fun(x); // ambiguous
}

class int_p
{
public:
int_p(int*ptr):ptr_(ptr){}
operator int*()const{ return ptr_; }
private:
int* ptr_;
};

void fun (int_p x) {} //fun1
void fun (int (&x)[2]){} //fun2

int main() {
int x[2];
int y[3];
fun(x); // calls fun2
fun(y); // calls fun1
}

Cheers,
Serge

I have no idea why it does what it does but it does it like a charm!
I'll be studing it for a while :)
Thanks
 
C

Canonical Latin

Let me thank again everyone who replied. I got some wonderful answers. I'm
going to read all of your posts again carefully and when I have digested all
of it, I'll post a summery.
 

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,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top