Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Array irregularites in C++

5 views
Skip to first unread message

Vidar Hasfjord

unread,
Dec 3, 2006, 8:26:07 PM12/3/06
to
This is a longer treatment of a reply in the "The D Programming
Language" thread.

Al wrote:

> And here's how it should be:
>
> int[] array = [1, 2, 3, 42, 666];
> cout << array.size << endl; // 5
>
>
> People simply /expect/ an array to automatically know its size.

Actually, an array in C++ knows its size --- its part of the type
(although the language will deceive you; more on that below). In native
C++, i.e. without using library features, the above is expressed as:

int a [] = {1, 2, 3, 42, 666};
cout << sizeof a / sizeof a [0] << endl; // 5

Of course the C/C++ array has static size, and some may argue that's
not very useful. I consider the static array the most fundamental
building block of a system programming language, because it is so
closely related to the von Neumann addressable memory model. In fact,
references/proxies, pointers, dynamic arrays and strings can be built
in terms of the static array.

Unfortunately, the irregular treatment of the native array in C++ (and
C) is IMHO the most fundamental and ugliest wart of the language. The
way its type decays when passed around is simply traitorous:

size_t size5 (int a [5])
{return sizeof a / sizeof a [0];} // = 1 (on 32-bit)!

Here the user explicitly states the type of the parameter, but the
language dismisses it and transforms it into a pointer type. The above
function signature is treated exactly equivalent to:

size_t size5 (int* a) // Ugh!

This is highly irregular and an unforgivable deceit! It means you can
pass any object that converts to "int*", including "int [4]" and other
array sizes. This is a hole in the type system, and the C heritage is
of course to blame. That said, C++ do put a band-aid on the problem by
retaining the type info when the array is passed as reference:

size_t size5r (int (&a) [5])
{return sizeof a / sizeof a [0];} // = 5

This version is properly type checked as well, i.e. you cannot pass an
"int [4]". This allows you to write generic functions that can handle
static arrays of any size:

template <typename T, int N>
size_t size (T (&a) [N])
{return sizeof a / sizeof a [0];} // = N

Still, there's no way of passing an array by value as for other native
types. Other irregularities of the native array include not being an
lvalue and (for that reason, I guess) lacking copy constructor and
assignment. The tr1::array (boost::array) class encapsulates a native
array and adds these features as well as std-like container behaviour.

I guess fixing these problems in the language without breaking existing
code is hard, although adding copy construction and assignment should
not affect any correct programs (since these operations produce errors
today). The array parameter passing irregularity is more difficult
because so many C-style programs depend on it. But what about
introducing the following rule: Any array parameter type that specifies
a size will not decay and will be passed by value. Arrays implicitly
convert to pointers as before. For example:

R foo (T a [5]); // proposal: doesn't decay; passed by value
R foo (T a []); // incomplete type: still decays to "T* a"
R foo (T* a); // still accepts an array "T a [1]; foo (a)".

I guess most existing programs that depend on the decaying type is
written in the second style, hence wont be affected by the change. This
style could then be deprecated, because it is easily rewritten in the
third style, i.e. by explicitly using a pointer type as parameter.

Has work been done to make the language more regular in this area? Or
is it deemed impossible/too hard to fix?


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Le Chaud Lapin

unread,
Dec 4, 2006, 10:18:29 AM12/4/06
to
Vidar Hasfjord wrote:
> Unfortunately, the irregular treatment of the native array in C++ (and
> C) is IMHO the most fundamental and ugliest wart of the language. The
> way its type decays when passed around is simply traitorous:

I could not agree more.

There are other irregularities in C/C++, but this one stands out the
most by far. Obviously Denis Ritchie probably felt he was doing us a
favor by prescribing automatic conversion from array name to pointer to
first element, but as you have stated, it might have been far more
beneficial to preserve regularity. One never knows when such
preservation will reward us with unforeseen benefits. I would have been
more than willing to rewrite all my code if C++ has fixed this. It's
probably too late now.

All things being equal, it is almost always better to stay on the path
of regularity.

-Le Chaud Lapin-

PeteK

unread,
Dec 4, 2006, 3:27:32 PM12/4/06
to
Vidar Hasfjord wrote:
> [SNIP] ...

> introducing the following rule: Any array parameter type that specifies
> a size will not decay and will be passed by value. Arrays implicitly
> convert to pointers as before. For example:
>
> R foo (T a [5]); // proposal: doesn't decay; passed by value
> R foo (T a []); // incomplete type: still decays to "T* a"

Why does this have to decay to T* a? Couldn't the array size be passed
as a hidden variable (for example as the this pointer is in member
functions)? Of course you'd need some way of accessing the information
e.g.:

for( int i = 0; i < length_of( a ); ++i)
{
do_something( a[i] );
}

> R foo (T* a); // still accepts an array "T a [1]; foo (a)".
>

...

Kevin Hall

unread,
Dec 4, 2006, 3:35:11 PM12/4/06
to

Vidar Hasfjord wrote:
> Unfortunately, the irregular treatment of the native array in C++ (and
> C) is IMHO the most fundamental and ugliest wart of the language. The
> way its type decays when passed around is simply traitorous:
>
> size_t size5 (int a [5])
> {return sizeof a / sizeof a [0];} // = 1 (on 32-bit)!
>
> [snipped]

>
> Has work been done to make the language more regular in this area? Or
> is it deemed impossible/too hard to fix?
>

The language itself cannot be fixed without breaking programs written a
long time ago. Fortunately, a standard library component is being
developed. From what I understand, there will be a std::tr2::array
(based on boost::array) included in the next technical report. Here's
a link:

http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1548.htm

Regards,

Kevin

Kevin Hall

unread,
Dec 4, 2006, 7:34:34 PM12/4/06
to

PeteK wrote:
> Vidar Hasfjord wrote:
> > [SNIP] ...
> > introducing the following rule: Any array parameter type that specifies
> > a size will not decay and will be passed by value. Arrays implicitly
> > convert to pointers as before. For example:
> >
> > R foo (T a [5]); // proposal: doesn't decay; passed by value
> > R foo (T a []); // incomplete type: still decays to "T* a"
>
> Why does this have to decay to T* a? Couldn't the array size be passed
> as a hidden variable (for example as the this pointer is in member
> functions)? Of course you'd need some way of accessing the information
> e.g.:
>

Becuase C decays this to 'T* a' and C++ attempts to retain
compatibility with C.

In C++, you could use a class like the following:

template <typename T>
class array_wrapper
{
T* data_;
size_t size_;

public:
template<size_t N>
array_wrapper(T (&a) [N])
: data_(a)
, size_(N)
{}

size_t size() const {return size_;}

T& operator[](size_t index) {return data_[index];}
const T& operator[](size_t index) const {return data_[index];}

// Other member functions, operators, even iterators...
};

void foo (array_wrapper<int> a)
{
std::cout << a.size() << std::endl;
}

int main()
{
int x[] = {1,2,3};

foo(x); // prints '3'
return 0;
}

- Kevin

Pete Becker

unread,
Dec 4, 2006, 7:35:46 PM12/4/06
to
Kevin Hall wrote:
>
> The language itself cannot be fixed without breaking programs written a
> long time ago. Fortunately, a standard library component is being
> developed. From what I understand, there will be a std::tr2::array
> (based on boost::array) included in the next technical report.

std::tr1::array is already there, in tr1.

--

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

Le Chaud Lapin

unread,
Dec 4, 2006, 7:33:15 PM12/4/06
to
PeteK wrote:
> Vidar Hasfjord wrote:
> > R foo (T a [5]); // proposal: doesn't decay; passed by value
> > R foo (T a []); // incomplete type: still decays to "T* a"
>
> Why does this have to decay to T* a? Couldn't the array size be passed
> as a hidden variable (for example as the this pointer is in member
> functions)? Of course you'd need some way of accessing the information
> e.g.:
>
> for( int i = 0; i < length_of( a ); ++i)
> {
> do_something( a[i] );
> }
>
> > R foo (T* a); // still accepts an array "T a [1]; foo (a)".

Being picky when it comes to preserving regularity, I would go as far
as to say that passing T a[] by value should not be allowed. Of
course, if a T a[N] is passed by value, then N is known at compile time
by examining the function prototype.

It _should_ be possible to pass a[] by reference, in which case the
type would remain "reference to array of T whose size has not been
specified.". The true size of the array would be passed explicitly as
a separate argument.

But I would elminate the "automatic converstion to pointer"
helpfulness. To get the conventional "pointer to first element of a,
programmers would have to write an expression that precisely indicates
that:

&a[0];

After the OP's post yesterday, I remember all the times where I had to
copy a large array of characters, and when searching for the most
optimal, portable way. If Ritchie had decided not to help us by the
automatic conversion, it would be possible to do ultra-fast memcpy's
using something akin to Duff's device, but for arrays:

void copy (char *source, *target)
{
*reinterpret_cast<"pointer to array of 1024 char">(target) =
*reinterpret_cast<"pointer to array of 1024 char">(source);
}

The compiler would know the fastest way to do the copy.

-Le Chaud Lapin-

Vidar Hasfjord

unread,
Dec 4, 2006, 7:44:12 PM12/4/06
to
Kevin Hall wrote:
> The language itself cannot be fixed without breaking programs written a
> long time ago. Fortunately, a standard library component is being
> developed. From what I understand, there will be a std::tr2::array
> (based on boost::array) included in the next technical report.

Yes, I was aware of tr1::array, and I should have mentioned it. (It is
actually in TR1, see section 6.2.) It is actually neat how a
fundamental flaw can be replaced by a UDT that fully works the way the
native type should have done; and with no overhead at that. It is at
testament to the expressivity of C++.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf

Al

unread,
Dec 5, 2006, 1:10:19 AM12/5/06
to
Hi,

Vidar Hasfjord wrote:
> Kevin Hall wrote:
>> The language itself cannot be fixed without breaking programs written a
>> long time ago. Fortunately, a standard library component is being
>> developed. From what I understand, there will be a std::tr2::array
>> (based on boost::array) included in the next technical report.
>
> Yes, I was aware of tr1::array, and I should have mentioned it. (It is
> actually in TR1, see section 6.2.) It is actually neat how a
> fundamental flaw can be replaced by a UDT that fully works the way the
> native type should have done; and with no overhead at that. It is at
> testament to the expressivity of C++.
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf

Um... is the size of these arrays fixed statically, or am I missing
something?

What I illustrated with my original example was a /dynamic/ array.

To reiterate:

int[] array = [1, 2, 3, 42, 666];

cout << array.size; // 5
array.pop();
cout << array.size; // 4
array.push(7, 8, 9);
cout << array.size; // 7
cout << array; // [1, 2, 3, 42, 7, 8, 9]
array.clear();
cout << array; // [], or null, or <empty>

Does std::tr1/2::array provide this functionality? Thanks.

Cheers,
-Al.

Vidar Hasfjord

unread,
Dec 5, 2006, 1:21:04 AM12/5/06
to
PeteK wrote:
> > R foo (T a []); // incomplete type: still decays to "T* a"
> Why does this have to decay to T* a? Couldn't the array size be passed
> as a hidden variable

Well, it could, and C99 variable-length arrays (VLAs) work like this,
although using the declaration syntax (T a [*]). But maybe your syntax
is more regular; "size unknown at compile time; determined at run-time
base on the argument passed". It also removes the decaying special
case. That said, it probably breaks existing programs.

> Of course you'd need some way of accessing the information

C99 does it like {void foo (int a [n])}

C99 also overloads the sizeof operator for VLAs --- a disputed design
choice.

Seungbeom Kim

unread,
Dec 5, 2006, 6:02:34 AM12/5/06
to
Le Chaud Lapin wrote:
> Vidar Hasfjord wrote:
>> Unfortunately, the irregular treatment of the native array in C++ (and
>> C) is IMHO the most fundamental and ugliest wart of the language. The
>> way its type decays when passed around is simply traitorous:
>
> I could not agree more.
>
> There are other irregularities in C/C++, but this one stands out the
> most by far. Obviously Denis Ritchie probably felt he was doing us a
> favor by prescribing automatic conversion from array name to pointer to
> first element, but as you have stated, it might have been far more
> beneficial to preserve regularity.

But without that, how could a function take arrays of different sizes?
In C++ we have overloading and templates, but that's not always
desirable, and in C there's simply no other way than to decay the array
into a pointer, a common type.

--
Seungbeom Kim

Kevin Hall

unread,
Dec 5, 2006, 6:05:02 AM12/5/06
to

Al wrote:

> Vidar Hasfjord wrote:
>
> > Yes, I was aware of tr1::array, and I should have mentioned it. (It is
> > actually in TR1, see section 6.2.) It is actually neat how a
> > fundamental flaw can be replaced by a UDT that fully works the way the
> > native type should have done; and with no overhead at that. It is at
> > testament to the expressivity of C++.
> >
> > http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf
>
> Um... is the size of these arrays fixed statically, or am I missing
> something?
>
> What I illustrated with my original example was a /dynamic/ array.
>
> To reiterate:
>
> int[] array = [1, 2, 3, 42, 666];
> cout << array.size; // 5
> array.pop();
> cout << array.size; // 4
> array.push(7, 8, 9);
> cout << array.size; // 7
> cout << array; // [1, 2, 3, 42, 7, 8, 9]
> array.clear();
> cout << array; // [], or null, or <empty>
>
> Does std::tr1/2::array provide this functionality? Thanks.

[embarrassed] Actually it is std::tr1::array.

Anyway, the answer is 'no'. std::tr1::array does not provide the
ability to push and pop elements. What you want is something like
std::queue, std::deque, or std::vector. The correct choice will depend
on the type of operations you will be using most frequently.

The bad thing is that you cannot initialize std::queue, std::deque, and
std::vector with initialization lists. This is where C++ could really
shine in the future.

Anyway, built-in support for dynamic arrays is IMHO not a wise
decision. The reason is that it is impossible for a compiler writer to
determine the best type of dynamic array for a given application. Only
the programmer knows that. But a general form of array-like
initialization lists would be extremely useful and that could be used
by built-ins, and both standard and non-standard library classes as
well. Even if they are only syntactic sugar, it still is an
improvement, as readability is often key for maintainability.

Regards,

Kevin

Vidar Hasfjord

unread,
Dec 5, 2006, 11:28:34 AM12/5/06
to
Al wrote:
> Hi,

> Um... is the size of these arrays fixed statically, or am I missing something?

That's correct. Arrays in C++ has always been static and probably
always will be. That includes the improved wrapper type tr1::array with
regular semantics.

> What I illustrated with my original example was a /dynamic/ array.

That may be. I didn't mean to imply one or the other; just point out
how native C++ static arrays work and discuss their flaws. Hence the
new thread.

Static arrays are building blocks for dynamic arrays. For example {char
a[65536][4];} is optimally space efficient because the size of the
inner array is really only 4 characters. The size is not stored (except
implicitly by code acting on it) and cannot be changed nor queried at
run-time. It is a basic, but important, building block.

Dynamic arrays are handled in C++ by std::vector, as you probably know,
which of course is built on the static array. Vectors are fully
dynamic, but of course, that entails costs in both memory and
performance.

C's variable-length array (VLA) is some kind of middle ground. It's not
as much a new type as it is a calling convention, giving a function the
ability to polymorphically handle arrays of different sizes (i.e.
types). VLA achieves this for compiled functions (one function instance
handles all array sizes), while C++ templates achieves this for source
(a specialized function is instantiated for each array size). Since C99
VLAs cannot be passed by-value anyway, a single function in C++ that
takes the array by pointer/reference and its size as two arguments
achieves the same functionality. VLAs on the stack may achieve some
performance gain, but it's basically only a question of where the
dynamic array is allocated, and C++ has ways to do efficient heap
allocations if optimal performance is key.

Jeffrey Yasskin

unread,
Dec 6, 2006, 1:23:59 AM12/6/06
to
On Dec 5, 3:05 am, "Kevin Hall" <kevindhall.j...@yahoo.com> wrote:
> But a general form of array-like
> initialization lists would be extremely useful and that could be used
> by built-ins, and both standard and non-standard library classes as
> well. Even if they are only syntactic sugar, it still is an
> improvement, as readability is often key for maintainability.

The initializer lists proposal by Stroustrup and Dos Reis
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2100.pdf> for
C++0x is exactly this. The first line of Al's example would be:

vector<int> array = {1, 2, 3, 42, 666};

Jeffrey

Al

unread,
Dec 6, 2006, 2:06:57 AM12/6/06
to
Hi there,

Vidar Hasfjord wrote:
> Al wrote:
>> Hi,
>> Um... is the size of these arrays fixed statically, or am I missing something?
>
> That's correct. Arrays in C++ has always been static and probably
> always will be. That includes the improved wrapper type tr1::array with
> regular semantics.
>
>> What I illustrated with my original example was a /dynamic/ array.
>
> That may be. I didn't mean to imply one or the other; just point out
> how native C++ static arrays work and discuss their flaws. Hence the
> new thread.

Alright.

> Static arrays are building blocks for dynamic arrays. For example {char
> a[65536][4];} is optimally space efficient because the size of the
> inner array is really only 4 characters. The size is not stored (except
> implicitly by code acting on it) and cannot be changed nor queried at
> run-time. It is a basic, but important, building block.

> Dynamic arrays are handled in C++ by std::vector, as you probably know,
> which of course is built on the static array. Vectors are fully
> dynamic, but of course, that entails costs in both memory and
> performance.

Yeah, I like vector and I consider it very useful. I would have no
qualms whatsoever with making all array literals default to this type
instead of an unsafe, nearly useless block of memory :).

However, I don't understand what you mean by vector being "built on the
static array." I don't know vector internals that well, but I'm almost
certain it will have to use new or malloc at some point when resizing
up. It might use _alloca somehow (?), but that's not a static array either.

Thus, I don't fully agree with your sentiment that static arrays are an
important building block (though I do agree they are basic :)). If
anything, that'd be a "nice" property to have, but, like compatibility
with C, it doesn't seem particularly critical and is probably even be
harmful.


> C's variable-length array (VLA) is some kind of middle ground. It's not
> as much a new type as it is a calling convention, giving a function the
> ability to polymorphically handle arrays of different sizes (i.e.
> types). VLA achieves this for compiled functions (one function instance
> handles all array sizes), while C++ templates achieves this for source
> (a specialized function is instantiated for each array size). Since C99
> VLAs cannot be passed by-value anyway, a single function in C++ that
> takes the array by pointer/reference and its size as two arguments
> achieves the same functionality. VLAs on the stack may achieve some
> performance gain, but it's basically only a question of where the
> dynamic array is allocated, and C++ has ways to do efficient heap
> allocations if optimal performance is key.

C99 VLAs are a modest improvement but an improvement nonetheless. It
isn't even necessary to attempt stack-based (e.g. _alloca, basically) to
implement VLAs. Just throw everything on the heap. If the compiler is
smart enough to allocate on the stack for performance gain, then great.
But it shouldn't be a necessity.

If a person doesn't want this /potential/ degradation in speed, that
person can just use a compile-time constant integral size and be done
with it. The compiler /could/ guarantee that such a size will always be
a static array.

Cheers,
-Al.

James Kanze

unread,
Dec 6, 2006, 11:36:27 AM12/6/06
to
Le Chaud Lapin wrote:
> Vidar Hasfjord wrote:
> > Unfortunately, the irregular treatment of the native array in C++ (and
> > C) is IMHO the most fundamental and ugliest wart of the language. The
> > way its type decays when passed around is simply traitorous:

> I could not agree more.

> There are other irregularities in C/C++, but this one stands out the
> most by far. Obviously Denis Ritchie probably felt he was doing us a
> favor by prescribing automatic conversion from array name to pointer to
> first element,

Actually, I doubt he thought of it in those terms at all. Denis
Ritchie was coming from a B background, and trying to adopt B to
use types (probably because the typeless paradigm used in B
makes no sense on a byte addressable machine). In B, everything
is a machine word, and the only way to make an array a machine
word is to allocate it somewhere, and initialize a machine word
with a pointer to it. He just carried the same concept over
into C (without actually creating the pointer variable).

--
James Kanze (GABI Software) email:james...@gmail.com
Conseils en informatique orientie objet/
Beratung in objektorientierter Datenverarbeitung
9 place Simard, 78210 St.-Cyr-l'Icole, France, +33 (0)1 30 23 00 34

James Kanze

unread,
Dec 6, 2006, 11:37:15 AM12/6/06
to
Seungbeom Kim wrote:
> Le Chaud Lapin wrote:
> > Vidar Hasfjord wrote:
> >> Unfortunately, the irregular treatment of the native array in C++ (and
> >> C) is IMHO the most fundamental and ugliest wart of the language. The
> >> way its type decays when passed around is simply traitorous:

> > I could not agree more.

> > There are other irregularities in C/C++, but this one stands out the
> > most by far. Obviously Denis Ritchie probably felt he was doing us a
> > favor by prescribing automatic conversion from array name to pointer to
> > first element, but as you have stated, it might have been far more
> > beneficial to preserve regularity.

> But without that, how could a function take arrays of different sizes?

By allowing pointers to an incomplete type.

> In C++ we have overloading and templates, but that's not always
> desirable, and in C there's simply no other way than to decay the array
> into a pointer, a common type.

A function which takes an array by value would have to take a
fixed size array. I don't really think that this is to
constraining---you don't really want to pass arrays by value
anyway. And if passing a pointer, there's no problem, since you
can have a pointer to an incomplete type. You'd end up with
something like:

void f( int a[3] ) ; // takes int[3] by value
void f( int a[] ) ; // illegal, can't pass
// incomplete type by value
void f( int (*a)[3] ) ; // takes address of an int[3]
void f( int (*a)[] ) ; // takes address of incomplete
// type int [].

I think it would have worked.

--
James Kanze (GABI Software) email:james...@gmail.com
Conseils en informatique orientie objet/
Beratung in objektorientierter Datenverarbeitung
9 place Simard, 78210 St.-Cyr-l'Icole, France, +33 (0)1 30 23 00 34


--

Le Chaud Lapin

unread,
Dec 6, 2006, 2:16:30 PM12/6/06
to
Seungbeom Kim wrote:
> But without that, how could a function take arrays of different sizes?
> In C++ we have overloading and templates, but that's not always
> desirable, and in C there's simply no other way than to decay the array
> into a pointer, a common type.

int a[16];

void f (int *, size_t size);

f(&a[0], sizeof(a));


The goal is not so much to allow a function to accept array of
different sizes, but to preserve type when there seems to be no good
reason to kill it.

-Le Chaud Lapin-


--

Le Chaud Lapin

unread,
Dec 6, 2006, 2:15:40 PM12/6/06
to
Al wrote:
> Thus, I don't fully agree with your sentiment that static arrays are an
> important building block (though I do agree they are basic :)). If
> anything, that'd be a "nice" property to have, but, like compatibility
> with C, it doesn't seem particularly critical and is probably even be
> harmful.

I am going to have to defend the OP on principle, since he and I seem
to think similarly on this issue.

You will notice in his original post, he used a peculiar word,
"regularity", which seems to carry more weight with some people than
others. I am one for whom it carries much weight.

A programmer to whom "regularity" matters will undoubtedly appreciate
the regularity of the language as it manifests in all it's forms. An
example I gave 2 years ago was...

"You're reading K&R. It says, 'A statement is an expression followed
by a semicolon.' Nothing else. You infer, "...well, if that's true, a
'7' followed by a semicolon must be a statement...", so you try it:

7;

And the compiler compiles it without incident. And rightly so, as a
rejection, in the absence of qualification of what constitutes an
expression would be a breach of regularity. One must not underestimate
this principle. It is the basis of functional orthongonality. It is
what permits reachability in system space, and allows an engineer to
think along a certain axis without regard for what happens on the other
axes.

The same could be said for static POD arrays. The programmer, by now
accustomed to the inherently pass-by-value semantics of C, sees the
proclamation by K&R: 'Arrays are different.'

Some programmers will read this, and immediately, a mental alarm will
go off..."..Denis Ritchie obviously has a good reason for doing this,
as to make arrays different constitutes a breach of regularity. I am
curious to see what he had in mind" It is only later that you discover
that the cause of the breach was nothing Earth-shattering. It was
merely to allow the programmer to type

char v[10];

1. f (v);

Instead of

2. f(&v[0]);

This breach is not with penalty. For some people (myself and probably
the OP), we gain mental relief from consistency. We would prefer #2
because it relieves us from having to constantly consider and
reconsider the breach:

"Oh..right...arrays are different...and no, I cannot pass my new 3x3
long double matrix as a whole...if I try, a pointer to the first
element will get passed instead...and typedef long double [3][3]
doesn't help...too bad...so...what's the type of that v thing
anyway...right, it is not of type 'vector of 10 char', it is of type
pointer to first element...but..what if I actually passed a type that
really was pointer to first element...a real pointer to char..I guess
it would be the same...I have to keep this in mind while using arrays.
I don't see why Ritchie did this...I certainly do not mind having to
type the extra characters & [0], and there might have been times were I
actually wanted to pass the whole array by value, but I cannot do that
now. The regularity has been breached, and it's carved in stone."

This is what goes through some programmers' mind when they see how
arrays are treated differently from other fundamental types.

-Le Chaud Lapin-

Andrei Alexandrescu (See Website For Email)

unread,
Dec 7, 2006, 5:55:56 AM12/7/06
to
Le Chaud Lapin wrote:
> A programmer to whom "regularity" matters will undoubtedly appreciate
> the regularity of the language as it manifests in all it's forms. An
> example I gave 2 years ago was...
>
> "You're reading K&R. It says, 'A statement is an expression followed
> by a semicolon.' Nothing else. You infer, "...well, if that's true, a
> '7' followed by a semicolon must be a statement...", so you try it:
>
> 7;
>
> And the compiler compiles it without incident. And rightly so, as a
> rejection, in the absence of qualification of what constitutes an
> expression would be a breach of regularity. One must not underestimate
> this principle. It is the basis of functional orthongonality. It is
> what permits reachability in system space, and allows an engineer to
> think along a certain axis without regard for what happens on the other
> axes.

That's a nice example. And brings to mind an issue that I thought of
recently: chained comparisons.

Perl 6 is slated to allow chained comparisons:

http://dev.perl.org/perl6/rfc/25.html

That is, multiple ordering comparisons will be allowed and will be
chained as per math rules:

if (a == b == c) {...} // are a, b, and c all equal?
if (a != b != c != a) {...} // are a, b, and c distinct?
if (a <= b < c) {...} // is b in [a, c)?
...

Such tests are frequent in scientific code, and are consistent with
expressions you'd find in a math book.

A nice practical shortcut is that expressions in between two comparison
operators are evaluated at most once. I'm saying "at most" because the
comparison may be never executed, e.g.:

if (a != b != c != a) {...} // are a, b, and c distinct?

If a == b, then the rest of comparisons are never evaluated, nor is c.

But, the behavior is inconsistent with the rest of the language. What's
consistent with the rest of the language is today's behavior, which is
to evaluate one comparison and then feed its result to the next
comparison. As a net result, chained comparisons almost always do
something useless and potentially dangerous for unwitting programmers.
Books spend time warning beginners to not use them. So nobody uses them.

The question is, what would be best to be consistent with? The language,
or the math?


Andrei

Vidar Hasfjord

unread,
Dec 7, 2006, 6:03:20 AM12/7/06
to
Le Chaud Lapin wrote:
> Obviously Denis Ritchie probably felt he was doing us a
> favor by prescribing automatic conversion from array name to pointer to
> first element, but as you have stated, it might have been far more
> beneficial to preserve regularity.

I don't think the implicit conversion is the main problem. The main
problem is the type decay of array parameters. I think it is important
to separate the two issues (the type of parameters; what is received,
and the type of arguments; what is passed). With regular semantics for
parameters of array type you would have full expressivity and regular
behavior for parameters even in the presence of implicit conversions of
arguments:

void foo (int a [5]);
void bar (int* a);
void osv (int a [5]);
void osv (int* a);

int a [5] = {1, 2, 3, 42, 666};
foo (a); // Pass by value.
bar (a); // Pass pointer to first element, via implicit conversion.
osv (a); // Calls the first overload since it's a better match.

So type decay is the real hurt to regularity.

Vidar Hasfjord

unread,
Dec 7, 2006, 9:39:17 AM12/7/06
to
Al wrote:
> Yeah, I like vector and I consider it very useful. I would have no
> qualms whatsoever with making all array literals default to this type

Unless I'm totally mistaken, there's no array literals in C/C++. There
are aggregate and array initializers but these are not expressions
(with type). They cannot appear anywhere other than in a declaration.
It's just syntactic sugar for element by element initialization. This
will change in C++0x, makin the language more regular in this area.

That said, I disagree that an array literal should have a dynamic array
type. It simply doesn't make sense. A literal is static (constant). Of
course, it could implicitly *convert* to a dynamic array though.

> instead of an unsafe, nearly useless block of memory :).

What's needed is a *safe* static array which is treated in a regular
way. That's tr1::array. The native array is broken. Until the native
array can be deprecated and removed from the language (in 50 years? 500
years?), the tr1::array should be taught and used. It is a fully
regular replacement.

> However, I don't understand what you mean by vector being "built on the
> static array." I don't know vector internals that well, but I'm almost
> certain it will have to use new or malloc at some point when resizing
> up. It might use _alloca somehow (?), but that's not a static array either.

Well, both are dealing with memory --- a static array. It's just a
matter of semantics. Look at it like this: A pointer is an iterator
over a static array. This is the semantic relationship Stepanov
identified and put to good use in the STL.

It is hence possible to define pointers in terms of arrays and
indexing. Thus a pointer need not be a native type. We could define
std::ptr as a replacement and then teach and use that until both the
native array and pointer types could be deprecated. As an aside, that
would leave a much more regular declaration syntax:

ptr <int> pi; // int*
ptr <array <int, 5>> pai; // int (*) [5]
array <ptr <int>, 5> api; // int* [5]
ptr <array <ptr <int>, 5>> papi; // int* (*) [5]

Vidar Hasfjord

unread,
Dec 7, 2006, 12:14:51 PM12/7/06
to
Le Chaud Lapin wrote:
> But I would elminate the "automatic converstion to pointer"
> helpfulness. To get the conventional "pointer to first element of a,
> programmers would have to write an expression that precisely indicates
> that:
>
> &a[0];

The problem with this is that it would break the usability of string
literals:

void say (const char* s);
say (&("Hello" [0])); // cryptic
say ((const char*) "Hello"); // deprecated style, unsafe
say (reinterpret_cast <const char*> ("Hello!")); // verbose, unsafe
say (static_cast <const char*> ("Hello!")); // need built-in support

So you probably would need to keep the implicit conversion from array
to pointer to first element. That said, a parameter of array type need
not decay:

void say (const char* s); // #1
void say (const char s [6]); // #2
say ("Hello"); // calls #2, better match
say ("Hello world!"); // calls #1, via implicit conversion

Note the above is hypothetical and won't compile (#1 and #2 redefines
the same function due to the type decay of arrays).

Vidar Hasfjord

unread,
Dec 7, 2006, 12:15:33 PM12/7/06
to
Andrei Alexandrescu (See Website For Email) wrote:
> The question is, what would be best to be consistent with? The language,
> or the math?

In my view the language is the better choice here. The language rule is
simpler and more regular. That said, it would be nice if it was
recognised and diagnosed since the mathematical notation is so
conventional. VC++ 2005 does:

int a, b;
if (a == b == 2) {/*...*/} // warning C4806*
if (a == b == 1) {/*...*/} // no warning, interp. as ((a == b) ==
true)

*warning C4806: '==' : unsafe operation: no value of type 'bool'
promoted to type 'int' can equal the given constant.

Gennaro Prota

unread,
Dec 7, 2006, 12:14:30 PM12/7/06
to
On 7 Dec 2006 05:55:56 -0500, Andrei Alexandrescu (See Website For
Email) wrote:

>Perl 6 is slated to allow chained comparisons:
>
>http://dev.perl.org/perl6/rfc/25.html
>
>That is, multiple ordering comparisons will be allowed and will be
>chained as per math rules:
>
>if (a == b == c) {...} // are a, b, and c all equal?
>if (a != b != c != a) {...} // are a, b, and c distinct?
>if (a <= b < c) {...} // is b in [a, c)?
>...
>
>Such tests are frequent in scientific code, and are consistent with
>expressions you'd find in a math book.

Borderline off-topic, but I've never seen the (equivalent of) the
second form in a math book. Or did you mean something other than
"pairwise distinct"?
--
Gennaro Prota. C++ developer. For hire.
(to mail me, remove any 'u' from the address)

Al

unread,
Dec 7, 2006, 7:53:59 PM12/7/06
to
Hi,

Vidar Hasfjord wrote:
> Al wrote:
>> Yeah, I like vector and I consider it very useful. I would have no
>> qualms whatsoever with making all array literals default to this type

> Unless I'm totally mistaken, there's no array literals in C/C++. There
> are aggregate and array initializers but these are not expressions
> (with type). They cannot appear anywhere other than in a declaration.
> It's just syntactic sugar for element by element initialization. This
> will change in C++0x, makin the language more regular in this area.

Yes, you're right. I was thinking of C99 compound literals for some
reason at the time, which do work with arrays. But it's just a matter of
time before they show up in some form in C++. Hopefully the next
standard, as you say.

> That said, I disagree that an array literal should have a dynamic array
> type. It simply doesn't make sense. A literal is static (constant).

I don't agree. This idea that a literal is static/constant in fact is
irregular by your own definition. If the language were /truly/ regular
in this regard, the following would be possible:

{1, 2, 3}.pop();

But instead, you are going to tell the programmer "Hey wait a second,
there is an exception, literals are different." Why? Notice that the
above is possible in many other current languages in similar form.

Then, there is another issue, when you mean the array is
static/constant, do you mean it's size, or its contents as well?

>Of course, it could implicitly *convert* to a dynamic array though.

How would this happen implicitly? You mean a copy is made behind the
scenes? What would the syntax look like?

>> instead of an unsafe, nearly useless block of memory :).
>
> What's needed is a *safe* static array which is treated in a regular
> way. That's tr1::array. The native array is broken. Until the native
> array can be deprecated and removed from the language (in 50 years? 500
> years?), the tr1::array should be taught and used. It is a fully
> regular replacement.

That is a great point. That's why I meant before. As long as native,
unsafe arrays exist with /much/ more convenient syntax, then people will
use them. Either fix them, or take them out. I say fix them.

>> However, I don't understand what you mean by vector being "built on the
>> static array." I don't know vector internals that well, but I'm almost
>> certain it will have to use new or malloc at some point when resizing
>> up. It might use _alloca somehow (?), but that's not a static array either.
>
> Well, both are dealing with memory --- a static array. It's just a
> matter of semantics. Look at it like this: A pointer is an iterator
> over a static array. This is the semantic relationship Stepanov
> identified and put to good use in the STL.

Well, I see your point, but if we're going with semantics then
everything is a static array. The whole memory space. In fact, the whole
program itself too, I guess. But how is that useful? It's only a
concept. It doesn't promote good style, safety, convenience, ease of use
or even re-usability. It's just an interesting property.

> It is hence possible to define pointers in terms of arrays and
> indexing. Thus a pointer need not be a native type. We could define
> std::ptr as a replacement and then teach and use that until both the
> native array and pointer types could be deprecated. As an aside, that
> would leave a much more regular declaration syntax:
>
> ptr <int> pi; // int*
> ptr <array <int, 5>> pai; // int (*) [5]
> array <ptr <int>, 5> api; // int* [5]
> ptr <array <ptr <int>, 5>> papi; // int* (*) [5]

I like the ptr syntax, since I try to avoid pointers. However, I'm not
convinced with that array syntax. Too verbose, I think. Interesting stuff!

Cheers,
-Al.

Lourens Veen

unread,
Dec 7, 2006, 7:52:03 PM12/7/06
to
Andrei Alexandrescu (See Website For Email) wrote:
>

I'd go for the math, at first thought, from a programmer/scientist
point of view. But I think we need more thought about how this would
work and what the corner cases would be before we can make a definite
assessment.

First, I wonder how you would define this. Consider

int a, b;
double c, d;

assert((a == b) == (c == d));

in current C++. There are three operators == here, with types

int x int -> bool
double x double -> bool
bool x bool -> bool

Equality of three values in the new system would be

int p, q, r;

assert(p == q == r);

but then what is the type of operator== here? It would have to be

int x int -> double

for the first instance, and

int x int -> bool

for the second. And how does the first operator signify whether the
two values are the same or not, if its return value is used to
propagate the second value? I agree that this seems unworkable.


Instead, a new n-ary operator== (and corresponding other operators)
could be defined, something like

template <class InputIterator>
bool operator==(InputIterator args_begin, InputIterator args_end);

but I'm not sure how the parsing rules would be adapted to be able to
distinguish between

int p, q, r;
bool b;

assert(p == q == r); // 1
assert(p == q == b); // 2

here. Or should line 2 simply be made illegal, and should we write

assert((p == q) == b);

instead if that is what we want?

Lourens

Le Chaud Lapin

unread,
Dec 7, 2006, 10:46:20 PM12/7/06
to
Andrei Alexandrescu (See Website For Email) wrote:
> Le Chaud Lapin wrote:
> The question is, what would be best to be consistent with? The language,
> or the math?

The language. We are allows to prefer it because it already preempts
the mathematical interpretation by prescribing piece-wise evaluation of
expressions.

int x = 8 / 3 * 3 / 8;

Mathematician says x = 1, programmer says x == 0, each noticing that
the other prefers a peculiar symbol to denote equality. ;)

-Le Chaud Lapin-

Le Chaud Lapin

unread,
Dec 7, 2006, 11:58:22 PM12/7/06
to
Vidar Hasfjord wrote:
> I don't think the implicit conversion is the main problem. The main
> problem is the type decay of array parameters. I think it is important
> to separate the two issues (the type of parameters; what is received,
> and the type of arguments; what is passed). With regular semantics for
> parameters of array type you would have full expressivity and regular
> behavior for parameters even in the presence of implicit conversions of
> arguments:
>
> void foo (int a [5]);
> void bar (int* a);
> void osv (int a [5]);
> void osv (int* a);
>
> int a [5] = {1, 2, 3, 42, 666};
> foo (a); // Pass by value.
> bar (a); // Pass pointer to first element, via implicit conversion.
> osv (a); // Calls the first overload since it's a better match.
>
> So type decay is the real hurt to regularity.
>

I certainly agree that type decay is the problem. However, we should
practice what we preach. If our goal is to keep the type system
regular, we should do that:

bar(&a[0]);

By allowing some decay automatically based on context, we violate the
very principle we champion. Yes, trivial conversions are already
permitted, but arrays are a bit like structs.

Also, I have not thought about it much, but I can imagine there might
be some complex situations where templates take array arguments. Note
that compiler writers have to sit down and think about these issues at
length. Imagine how simpler their jobs would be if they could do away
with the implicit conversion altogether.

I would not deviate from the path of regularity at all, at least not
for such a trivial convenience.

-Le Chaud Lapin-

Seungbeom Kim

unread,
Dec 8, 2006, 12:01:48 AM12/8/06
to
Vidar Hasfjord wrote:
> Unless I'm totally mistaken, there's no array literals in C/C++.

Except the string literals. :)

> That said, I disagree that an array literal should have a dynamic array
> type. It simply doesn't make sense. A literal is static (constant). Of
> course, it could implicitly *convert* to a dynamic array though.

Can you explain what you mean by "static" and "dynamic" array types?

> It is hence possible to define pointers in terms of arrays and
> indexing. Thus a pointer need not be a native type. We could define
> std::ptr as a replacement and then teach and use that until both the
> native array and pointer types could be deprecated. As an aside, that
> would leave a much more regular declaration syntax:
>
> ptr <int> pi; // int*
> ptr <array <int, 5>> pai; // int (*) [5]
> array <ptr <int>, 5> api; // int* [5]
> ptr <array <ptr <int>, 5>> papi; // int* (*) [5]

What benefits, other than the declaration syntax regularity,
does this give you over the plain pointers?

--
Seungbeom Kim

Andrei Alexandrescu (See Website For Email)

unread,
Dec 8, 2006, 12:57:47 PM12/8/06
to
Gennaro Prota wrote:
> On 7 Dec 2006 05:55:56 -0500, Andrei Alexandrescu (See Website For
> Email) wrote:
>
>> Perl 6 is slated to allow chained comparisons:
>>
>> http://dev.perl.org/perl6/rfc/25.html
>>
>> That is, multiple ordering comparisons will be allowed and will be
>> chained as per math rules:
>>
>> if (a == b == c) {...} // are a, b, and c all equal?
>> if (a != b != c != a) {...} // are a, b, and c distinct?
>> if (a <= b < c) {...} // is b in [a, c)?
>> ...
>>
>> Such tests are frequent in scientific code, and are consistent with
>> expressions you'd find in a math book.
>
> Borderline off-topic, but I've never seen the (equivalent of) the
> second form in a math book. Or did you mean something other than
> "pairwise distinct"?

Maybe "pairwise distinct" is not the right term. I meant that a, b, and
c are all distinct. The formula is definitely a classic; I remember
having pondered (when I was a kid) about how smart and concise the
formula is.


Andrei

Andrei Alexandrescu (See Website For Email)

unread,
Dec 8, 2006, 1:07:10 PM12/8/06
to
Lourens Veen wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> The question is, what would be best to be consistent with? The
>> language, or the math?
>
> I'd go for the math, at first thought, from a programmer/scientist
> point of view. But I think we need more thought about how this would
> work and what the corner cases would be before we can make a definite
> assessment.
>
> First, I wonder how you would define this. Consider
>
> int a, b;
> double c, d;
>
> assert((a == b) == (c == d));
>
> in current C++. There are three operators == here, with types
>
> int x int -> bool
> double x double -> bool
> bool x bool -> bool

The parentheses unambiguously compare the Boolean outcomes of the two
operations. In math you'd interpret things the same way, except probably
that you'd use the logical equivalence symbol <=> for clarity. There is
no source of confusion: if comparing a and b for equality yields the
same as comparing c and d for equality, then...

> Equality of three values in the new system would be
>
> int p, q, r;
>
> assert(p == q == r);
>
> but then what is the type of operator== here? It would have to be
>
> int x int -> double
>
> for the first instance, and
>
> int x int -> bool
>
> for the second. And how does the first operator signify whether the
> two values are the same or not, if its return value is used to
> propagate the second value? I agree that this seems unworkable.

That's not the way things are supposed to work. The individual
operator== implementations are unchanged. They still need to take two
values and return bool. The compiler, however, transforms the code:

assert(p == q == r);

into:

assert(p == q && q == r);

with the note that q is only evaluated once. Once the transformation is
done, regular semantics of operator== apply.

> Instead, a new n-ary operator== (and corresponding other operators)
> could be defined, something like

[snip]

That's not necessary.


Andrei

Nevin :-] Liber

unread,
Dec 8, 2006, 1:11:00 PM12/8/06
to
In article <J9vL5...@beaver.cs.washington.edu>,

"Andrei Alexandrescu (See Website For Email)"
<SeeWebsit...@erdani.org> wrote:

> That is, multiple ordering comparisons will be allowed and will be
> chained as per math rules:
>
> if (a == b == c) {...} // are a, b, and c all equal?
> if (a != b != c != a) {...} // are a, b, and c distinct?
> if (a <= b < c) {...} // is b in [a, c)?
> ...
>
> Such tests are frequent in scientific code, and are consistent with
> expressions you'd find in a math book.

"Consistent with" is stretching things a little bit. Math books are
usually asserting that these things are equal/not equal/etc., not
actually questioning ('if') whether or not they are.

> A nice practical shortcut is that expressions in between two comparison
> operators are evaluated at most once. I'm saying "at most" because the
> comparison may be never executed, e.g.:
>
> if (a != b != c != a) {...} // are a, b, and c distinct?
>
> If a == b, then the rest of comparisons are never evaluated, nor is c.
>

> The question is, what would be best to be consistent with? The language,
> or the math?

Although it is a really neat feature to have*, fitting in with the
language is far more important. Otherwise, you start having to invent
even more special case syntax/semantics to handle things like:

fn(bool z)
{
if ((a == b) == z) //...
}

Ultimately, it just makes the language harder to program in.


*Icon has this feature, and it fits in that language in a general way,
because testing ('if') whether an expression succeeds or fails is
separated from the resultant value of that expression (in the case of
binary operators, a reference to whatever is on the right hand side).

--
Nevin ":-)" Liber <mailto:ne...@eviloverlord.com> 773 961-1620

Lourens Veen

unread,
Dec 9, 2006, 2:15:04 AM12/9/06
to
Andrei Alexandrescu (See Website For Email) wrote:

> Lourens Veen wrote:
>> Andrei Alexandrescu (See Website For Email) wrote:
>>> The question is, what would be best to be consistent with? The
>>> language, or the math?
>>
>> I'd go for the math, at first thought, from a programmer/scientist
>> point of view. But I think we need more thought about how this
>> would work and what the corner cases would be before we can make a
>> definite assessment.
>>
>> First, I wonder how you would define this. Consider
>>
>> int a, b;
>> double c, d;
>>
>> assert((a == b) == (c == d));
>>
>> in current C++. There are three operators == here, with types
>>
>> int x int -> bool
>> double x double -> bool
>> bool x bool -> bool
>
> The parentheses unambiguously compare the Boolean outcomes of the
> two operations. In math you'd interpret things the same way, except
> probably that you'd use the logical equivalence symbol <=> for
> clarity. There is no source of confusion: if comparing a and b for
> equality yields the same as comparing c and d for equality, then...

I wasn't claiming this to be confusing; it was merely intended to be a
contrasting introduction to the following. What I wanted to say was
that in this case it's clear what's happening and there is only one
option in each of the three cases, in the case below there are two
equivalent situations that require choosing a different operator.

>> Equality of three values in the new system would be
>>
>> int p, q, r;
>>
>> assert(p == q == r);
>>
>> but then what is the type of operator== here? It would have to be
>>
>> int x int -> double
>>
>> for the first instance, and
>>
>> int x int -> bool
>>
>> for the second. And how does the first operator signify whether the
>> two values are the same or not, if its return value is used to
>> propagate the second value? I agree that this seems unworkable.
>
> That's not the way things are supposed to work. The individual
> operator== implementations are unchanged. They still need to take
> two values and return bool. The compiler, however, transforms the
> code:
>
> assert(p == q == r);
>
> into:
>
> assert(p == q && q == r);
>
> with the note that q is only evaluated once. Once the transformation
> is done, regular semantics of operator== apply.

And then

bool b;
int x, y;

assert(x == y == b);

would yield

assert(x == y && y == b);

which is probably not quite what the programmer had in mind. Or should
this be illegal then? I would probably write

assert((x == y) == b);

to make this easier to read anyway, so I don't think that that is a
problem per se (of course, this potentially breaks existing code).

Lourens

Andrei Alexandrescu (See Website For Email)

unread,
Dec 9, 2006, 2:13:17 AM12/9/06
to
Le Chaud Lapin wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>
>>Le Chaud Lapin wrote:
>>The question is, what would be best to be consistent with? The language,
>>or the math?
>
>
> The language. We are allows to prefer it because it already preempts
> the mathematical interpretation by prescribing piece-wise evaluation of
> expressions.
>
> int x = 8 / 3 * 3 / 8;
>
> Mathematician says x = 1, programmer says x == 0, each noticing that
> the other prefers a peculiar symbol to denote equality. ;)

I think that's not a comparable example. A mathematician might easily
ingest the explanation that, for good reasons, division among integers
is integral division. In math it does happen that the same symbol has
various roles depending on context. But with comparisons the issue is
different. So I doubt they'd be as vexed as with a == b == c.


Andrei

Andrei Alexandrescu (See Website For Email)

unread,
Dec 9, 2006, 10:01:58 PM12/9/06
to
Nevin :-] Liber wrote:
> In article <J9vL5...@beaver.cs.washington.edu>,
> "Andrei Alexandrescu (See Website For Email)"
> <SeeWebsit...@erdani.org> wrote:
>
>
>>That is, multiple ordering comparisons will be allowed and will be
>>chained as per math rules:
>>
>>if (a == b == c) {...} // are a, b, and c all equal?
>>if (a != b != c != a) {...} // are a, b, and c distinct?
>>if (a <= b < c) {...} // is b in [a, c)?
>>...
>>
>>Such tests are frequent in scientific code, and are consistent with
>>expressions you'd find in a math book.
>
>
> "Consistent with" is stretching things a little bit. Math books are
> usually asserting that these things are equal/not equal/etc., not
> actually questioning ('if') whether or not they are.

There's no stretch. Functions defined by cases do use "if" conditions.

>>The question is, what would be best to be consistent with? The language,
>>or the math?
>
> Although it is a really neat feature to have*, fitting in with the
> language is far more important. Otherwise, you start having to invent
> even more special case syntax/semantics to handle things like:
>
> fn(bool z)
> {
> if ((a == b) == z) //...
> }
>
> Ultimately, it just makes the language harder to program in.

That wouldn't be another special case. It makes it clear that I want to
compare whatever a == b yields, with z. That's a different thing from a
== b == z. In existing language, (a = b) = c is already very different
from a = b = c.


Andrei

--

Vidar Hasfjord

unread,
Dec 9, 2006, 10:18:00 PM12/9/06
to
Seungbeom Kim wrote:
> Except the string literals. :)

True. It's a good example of another irregularity though --- you can
have one type of array literal, but not another.

Another peculiar irregularity of string literals is that
zero-termination is built-in. That is (sizeof "Hello") is 6 since a
'\0' is appended. In my view zero-termination is a design-choice better
left to the programmer (or standard library) than imposed by the core
language. It is perfectly reasonable to work with strings (or more
generally, arrays) with no termination-element. In particular a static
array/literal knows its size because it is part of the type. For
example (hypothetical):

typedef char AccessCode [4]; // sizeof AccessCode == 4.
void foo (AccessCode a) { // No type decay - by value.
AccessCode b = a; // Copies (char [4]) object.
//...
}
AccessCode c = "42"; // Error - wrong size.
AccessCode d = "*42*"; // OK - copy initialization.
foo (d); // OK - pass by value.
foo ("42"); // Error - wrong size.
foo ("*42*"); // OK - pass by value.

For a program that works with a large number of static strings
zero-termination is wasteful. For example, the following is currently
illegal:

typedef char TypeTag [4];
TypeTag types [] = {"INTR", "CHAR", "ACOD", "PSWD", ...};
// error C2117: 'types' : array bounds overflow

You have to do:

TypeTag types [] = {{'I','N','T','R'}, ...};

All of this of course only applies to strings of static size. But in my
view they are the building blocks. Where dynamic size is needed you
pass pointer-and-size; preferably encapsulated in an abstraction such
as std::string.

> Can you explain what you mean by "static" and "dynamic" array types?

Although "static" has many overloaded meanings in C++, I've grown used
to the meaning "determined at compile-time". Conversely, I use
"dynamic" in the meaning "not known until run-time". I apply this
terminology to arrays as well any other language construct.

I find this a very useful terminology, especially in the context of the
generic programming and meta-programming developments in C++ over the
last decade. The established view now is that the template feature is a
functional programming language in itself, working in the compile-time
domain (the static realm), and computing types and constants. More
support for static computations are proposed for C++0x, esp. constant
folding.

But I digress. I should probably be more careful with terminology with
regard to arrays. By static array I mean an array type that has static
size (using the meaning of static above). A literal is both a static
array and a *constant* --- the latter meaning its elements are also
static, i.e. determined at compile-time.

Whenever you cross the static/dynamic (compile-time/run-time) boundary,
you have to store/pass along the static information. For example
(hypothetically regular):

char (*s) [5] = new char [5]; // Regular (not C++).
char* t = new char [5]; // Error - type mismatch.
*s = "Hello"; // Copy.
*s = "Hello!"; // Error - type mismatch.

// Dynamic memory allocations need size info:
const size_t buf_size = get_size_from_config_file ();
char* buf = malloc <char> (buf_size);

// Ordinary function - need size info:
str_copy (buf, buf_size, *s, sizeof *s);

// Template overload - deduces static size info:
str_copy (buf, buf_size, *s);

// No need for delete [], static size is deduced:
delete s;

// Dynamic memory deallocations need size info:
free <char> (buf, buf_size);

Note that a regular language would change the semantics of "new" for
array types. This would eliminate the need for "new []" and "delete
[]". But a new mechanism for allocating memory of a given size at
run-time would be needed, because "T a [n]", where "n" is a variable,
is illegal. This is "malloc" above. It could be implemented like
follows, based on the view that memory is a static array:

template <class T>
T* malloc (size_t n) {
// typedef char Memory [size_t];
size_t i = std::allocate (n * sizeof T);
return std::ptr <T> (i);
}

Hence, there's no need to introduce "T a [n]" (VLA) into the language.

>> ptr <int> pi; // int*

> What benefits, other than the declaration syntax regularity,
> does this give you over the plain pointers?

Now you're asking me to diverge from the thread topic. I think
regularity in itself has many virtues, many of which has been pointed
out before in this thread and elsewhere. To reiterate: Teachability,
usability, simpler compilers, easier tool making, etc. Hence, I think
it is worth consideration in itself. Here's my hypothetical example
above in nice regular syntax:

alias S = array <char, 5>; // Convenience.
auto s = new S; // Type deduction.
*s = "Hello"; // Copy.

// Dynamic allocations need size info:
auto buf_size = get_size_from_config_file ();
auto buf = malloc <char> (buf_size);

The rest of the example is identical. Another nicety of regular syntax
is the regular specification of type modifiers such as const:

const ptr <int> cpi; // int* const cpi;
ptr <const int> pci; // const int* pci; // or
// int const* pci;

const ptr <volatile array <const int>> cpvaci;

I'll leave the equivalent C++ declaration for the last one as an
exercise for the reader... :-)


--

Vidar Hasfjord

unread,
Dec 9, 2006, 10:29:00 PM12/9/06
to
Al wrote:
> > A literal is static (constant).
> I don't agree.

Then we are disagreeing on terminology. :-)

Using my terminology (see my reply to Seungbeom Kim) a literal exists
in the compile-time realm, and its size and contents are known. That
doesn't prevent any dynamic structure to be initialized by a literal,
of course.

> This idea that a literal is static/constant in fact is
> irregular by your own definition.

That may be so, but there's a limit to regularity as well. If the
language ignores some fundamental property, such as the "compile-time"
vs "run-time" determinacy discussed here, the language sacrifices
expressivity for uniformity.

> If the language were /truly/ regular
> in this regard, the following would be possible:
>
> {1, 2, 3}.pop();

In my view, the reason why this should not be allowed is because you
try to mutate a constant. This is a regular rule.

Without the ability to express a static (compile-time) property the
language becomes less expressive. You can clearly deduce that the
written expression "{1, 2, 3}" contains three elements of int. If the
language uses the "{}" braces to denote arrays, then you can go on to
deduce that the expression is an "array <int, 3>"; a static constant.
If the latter is not expressible you lose something (generic
programming and meta-programming is the processing of static entities;
types and constants).

That is not to say that something like what you wrote shouldn't be
possible. But conceptually it expresses a conversion of a static
construct to a dynamic one. I prefer to make that clear:

// Initialize a stack with an array literal.
// typeid ({1, 2, 3}) == typeid (array <int, 3>)
stack <int> ({1, 2, 3}).pop ();

// The above requires a stack constructor:
template <class T, size_t N>
stack <T>::stack (array <T, N>&);

This conveniently allows you to initialize many types of dynamic
structures by literals, which of course is something C++ already
depends on.

While distinguishing between static and dynamic constructs implies
differences, it doesn't have to imply irregularity or lack of safety,
and both type of constructs can be designed to work very similarly both
conceptually and syntactically. For example:

array <int, 3> a; // Static constant size.
const vector <int> b (3); // Constant size.
a = array <int, 3> (); // Error: const violation.
b = vector <int> (); // Error: const violation.
a.push_back (1); // Error: syntax error.
b.push_back (1); // Error: const violation.
int i = a [5]; // Error: out-of-bounds.
int j = b [5]; // Error: out-of-bounds.

The latter two may not be detected in C++ at compile-time, but
theoretically nothing prevents it. It is possible to deduce the sizes
of both the array and the vector. So a static type is very similar to a
constant dynamic type. In the extreme you could define array as a
synonym for a const vector:

template <class T, size_t N>
alias array = const vector <T, N>;

In short, there need not be much irregularity between static and
dynamic types.

> >Of course, it could implicitly *convert* to a dynamic array though.
> How would this happen implicitly? You mean a copy is made behind the
> scenes? What would the syntax look like?

C++ does this all the time:

float f = 1; // implicitly converted
void print (std::string s);
print ("Hello"); // implicitly converted

In C++0x, the following will (hopefully) be possible:

void print (std::vector <int> v);
print ({1, 2, 3}); // implicitly converted

> As long as native, unsafe arrays exist with /much/ more convenient
> syntax, then people will use them.

See my reply to Seungbeom Kim for some really nice and regular syntax.

> >> However, I don't understand what you mean by vector being

> >> "built on the static array." [...]


> > Well, both are dealing with memory --- a static array.

> Well, I see your point, but if we're going with semantics then
> everything is a static array. The whole memory space. In fact, the whole
> program itself too, I guess. But how is that useful?

It's obviously very useful; you just described the von Neumann
architecture in terms of a basic language construct. That's expressive!
:-)

Vidar Hasfjord

unread,
Dec 9, 2006, 10:45:49 PM12/9/06
to
Lourens Veen wrote:

> I'd go for the math, at first thought, from a programmer/scientist
> point of view. But I think we need more thought about how this would
> work and what the corner cases would be before we can make a definite
> assessment.

I think n-ary operators is a too irregular addition to the language. My
suggestion is to use (the proposed) variadic template functions with
much of the same convenience:

lt (a, b, c) // == (a < b && b < c)
gt (a, b, c) // == lt (c, b, a)
le (a, b, c) // == !lt (c, b, a)
ge (a, b, c) // == !lt (a, b, c)
eq (a, b, c) // == (a == b && b == c)
ne (a, b, c) // == !eq (a, b, c)

The templates could ensure transitivity explicitly if wanted.

You could use meta-programming to allow chaining different comparison
functions. This requires returning a proxy object convertible to bool
rather than a bool value. Here is Andrei's original example expressed
this way:

le (a, b).lt (c) // == (a <= b && b < c)

The nicest thing about this is that it is expressible by a general
language feature (variadic templates) rather than requiring an
irregular language extension.

Andrei Alexandrescu (See Website For Email)

unread,
Dec 10, 2006, 12:42:30 AM12/10/06
to
In-Reply-To: <bc750$4579c4cc$8259a2fa$18...@news2.tudelft.nl>
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Lourens Veen wrote:
> > And then
> >
> > bool b;
> > int x, y;
> >
> > assert(x == y == b);
> >
> > would yield
> >
> > assert(x == y && y == b);
> >
> > which is probably not quite what the programmer had in mind. Or should
> > this be illegal then?

The shape of the comparisons determines the transformation, not the
types of the expressions.

> > I would probably write
> >
> > assert((x == y) == b);
> >
> > to make this easier to read anyway, so I don't think that that is a
> > problem per se (of course, this potentially breaks existing code).

That is correct.


Andrei

Le Chaud Lapin

unread,
Dec 10, 2006, 12:43:06 AM12/10/06
to
Andrei Alexandrescu (See Website For Email) wrote:
> > I think that's not a comparable example. A mathematician might easily
> > ingest the explanation that, for good reasons, division among integers
> > is integral division. In math it does happen that the same symbol has
> > various roles depending on context. But with comparisons the issue is
> > different. So I doubt they'd be as vexed as with a == b == c.

But you would have to inform such mathematicians in advanced that
piece-wise evaluation of expressions is already prescribed by C++. You
would have to first show them the entire palette of C++ operators, the
rules governing them, the C++ reference manual, the ANSI standard, and
after all of that, ask,

"Being familiar with the prescribed mood and intent of the behavior of
these binary (and ternary) operators, are you still be willing to make
an exception for N-ary comparisons like <= <= <= <=?"

I would think many mathematicians would say no, even those who don't
know how to program computers. They would be more concerned with
preserving consistency with the context of the C++, just as they would
expect us to be reciprocally concerned with preserving consistency
within the context of mathematics. They might say, "Well, if you're
going to start making those kind of changes to the C++, make sure you
do it everywhere, not just on these comparison operators."

I guess the question here is whether we should breach piece-wise
evaluation so that C++ more closely resemble semantics of mathematical
notation. I would say no. The notion that expressions have:

1. type
2. value

is so firmly ingrained in my brain that, that I never make the < x < y
< z mistake. _However_, if someone had suggested long ago that we use
Pascal's operator for assignment, :=, I *would( have been OK with that.
Not allowing a plain = would have been a small benefit. But even in
this latter case, I have learned to slow down and be cautious, if only
for an instant, so that I don't write

=

when I actually meant

==

-Le Chaud Lapin-

peter koch larsen

unread,
Dec 10, 2006, 12:40:31 AM12/10/06
to

Vidar Hasfjord skrev:
[snip]
> > Dynamic arrays are handled in C++ by std::vector, as you probably know,
> > which of course is built on the static array. Vectors are fully
> > dynamic, but of course, that entails costs in both memory and
> > performance.
> >
That is not quite correct. There is no static array hidden inside a
std::vector and there can not be one in a conforming implementation
(nor can there be any new T[]).

/Peter
[snip]

Andrei Alexandrescu (See Website For Email)

unread,
Dec 10, 2006, 1:35:34 PM12/10/06
to
Vidar Hasfjord wrote:

> Lourens Veen wrote:
>
>
>>I'd go for the math, at first thought, from a programmer/scientist
>>point of view. But I think we need more thought about how this would
>>work and what the corner cases would be before we can make a definite
>>assessment.
>
>
> I think n-ary operators is a too irregular addition to the language.

Me too. The thing is, n-ary operators joined the discussion as result of
a misunderstanding. The operators are still binary, just that they chain.

> My
> suggestion is to use (the proposed) variadic template functions with
> much of the same convenience:
>
> lt (a, b, c) // == (a < b && b < c)
> gt (a, b, c) // == lt (c, b, a)
> le (a, b, c) // == !lt (c, b, a)
> ge (a, b, c) // == !lt (a, b, c)
> eq (a, b, c) // == (a == b && b == c)
> ne (a, b, c) // == !eq (a, b, c)

That's nice, but as soon as we think of things like a < b == c, there's
a combinatorial explosion of functions that ought to be defined.

> The templates could ensure transitivity explicitly if wanted.
>
> You could use meta-programming to allow chaining different comparison
> functions. This requires returning a proxy object convertible to bool
> rather than a bool value. Here is Andrei's original example expressed
> this way:
>
> le (a, b).lt (c) // == (a <= b && b < c)
>
> The nicest thing about this is that it is expressible by a general
> language feature (variadic templates) rather than requiring an
> irregular language extension.

With that syntax, variadics aren't even needed. Just group things two by
two and you're done. For UDTs, you can even have a <= b < c do what you
want (with each comparitor returning a specific type). My only point is
that a <= b < c is intuitive, while le(a, b).lt(c) sends the reader to
the Fortran manual :o).


Andrei

Andrei Alexandrescu (See Website For Email)

unread,
Dec 10, 2006, 1:41:39 PM12/10/06
to
Le Chaud Lapin wrote:
[on chained comparisons]

> Andrei Alexandrescu (See Website For Email) wrote:
> I would think many mathematicians would say no, even those who don't
> know how to program computers. They would be more concerned with
> preserving consistency with the context of the C++, just as they would
> expect us to be reciprocally concerned with preserving consistency
> within the context of mathematics. They might say, "Well, if you're
> going to start making those kind of changes to the C++, make sure you
> do it everywhere, not just on these comparison operators."

I wouldn't think so, but the discussion cannot be taken further as it's
unlikely either of us will actually run such an experiment.

> I guess the question here is whether we should breach piece-wise
> evaluation so that C++ more closely resemble semantics of mathematical
> notation. I would say no. The notion that expressions have:
>
> 1. type
> 2. value
>
> is so firmly ingrained in my brain that, that I never make the < x < y
> < z mistake.

(Your notion would not be shaken by chained comparisons. It's simply
that a < b < c is gramatically a different expression than a < b. The
grammar unambiguously figures one case from another, as it does in
myriad other places, such as `sizeof 5 - 1` or unary vs. binary `-`).

I don't make that mistake either, but my point was less about avoiding
mistakes, as simply some musings over what consistency really is, and
where it should adhere to. We have a clear example of useless/risky
consistency here, so it does look like consistency is not always an
ideal to follow.

kwikius

unread,
Dec 10, 2006, 4:52:47 PM12/10/06
to

Vidar Hasfjord wrote:
> Kevin Hall wrote:
> > The language itself cannot be fixed without breaking programs written a
> > long time ago. Fortunately, a standard library component is being
> > developed. From what I understand, there will be a std::tr2::array
> > (based on boost::array) included in the next technical report.
>
> Yes, I was aware of tr1::array, and I should have mentioned it. (It is
> actually in TR1, see section 6.2.) It is actually neat how a
> fundamental flaw can be replaced by a UDT that fully works the way the
> native type should have done;

It doesnt because you need to explicitly specify the size:

std::tr1::array<vect<value_type> >,??? > ={
vect<value_type>(0,0),
vect<value_type>(1,0),
vect<value_type>(1,1),
vect<value_type>(0,1),
vect<value_type>(0,0),
vect<value_type>(2,0),
vect<value_type>(2,1),
vect<value_type>(0,1),
vect<value_type>(0,0),
vect<value_type>(1,0),
vect<value_type>(1,1),
vect<value_type>(0,1),
vect<value_type>(0,0),
vect<value_type>(2,0),
vect<value_type>(2,1),
vect<value_type>(0,1),
vect<value_type>(0,0),
vect<value_type>(1,0),
vect<value_type>(1,1),
vect<value_type>(0,1),
vect<value_type>(0,0),
vect<value_type>(2,0),
vect<value_type>(2,1),
vect<value_type>(0,1),
vect<value_type>(0,0),
vect<value_type>(1,0),
vect<value_type>(1,1),
vect<value_type>(0,1),
vect<value_type>(0,0),
vect<value_type>(2,0),
vect<value_type>(2,1),
vect<value_type>(0,1),
vect<value_type>(0,0),
vect<value_type>(1,0),
vect<value_type>(1,1),
vect<value_type>(0,1),
vect<value_type>(0,0),
vect<value_type>(2,0),
vect<value_type>(2,1),
vect<value_type>(0,1)
};

regards
Andy Little

Vidar Hasfjord

unread,
Dec 10, 2006, 5:06:13 PM12/10/06
to
peter koch larsen wrote:
>>> Dynamic arrays are handled in C++ by std::vector, as you probably know,
>>> which of course is built on the static array.

> That is not quite correct. There is no static array hidden inside a


> std::vector and there can not be one in a conforming implementation
> (nor can there be any new T[]).

You're right; my language was poor. I didn't mean that vector has a
static array as a *part*. You cannot declare a static array with a size
determined at run-time, so you must use pointers in the implementation
of vector.

What I was thinking of was the relationship between pointers and arrays
in C/C++. Conceptually a pointer can be thought of as an iterator over
a static array covering the whole address space.

Lourens Veen

unread,
Dec 10, 2006, 6:10:57 PM12/10/06
to
Vidar Hasfjord wrote:

> Lourens Veen wrote:
>
>> I'd go for the math, at first thought, from a programmer/scientist
>> point of view. But I think we need more thought about how this
>> would work and what the corner cases would be before we can make a
>> definite assessment.
>
> I think n-ary operators is a too irregular addition to the language.
> My suggestion is to use (the proposed) variadic template functions
> with much of the same convenience:
>
> lt (a, b, c) // == (a < b && b < c)
> gt (a, b, c) // == lt (c, b, a)
> le (a, b, c) // == !lt (c, b, a)
> ge (a, b, c) // == !lt (a, b, c)
> eq (a, b, c) // == (a == b && b == c)
> ne (a, b, c) // == !eq (a, b, c)

Hmm. You could use operator overloading with that.

#include <iostream>
#include <ostream>

template <typename T>
class MathExpr {
public:
MathExpr() : value_(true) {}

MathExpr & operator>>(const T & t) {
t_ = t;
return *this;
}

MathExpr & operator<(const T & t) {
if (value_) {
value_ = value_ && (t_ < t);
t_ = t;
}
return *this;
}

MathExpr & operator==(const T & t) {
if (value_) {
value_ = value_ && (t_ == t);
t_ = t;
}
return *this;
}

// undefined, catches problems
MathExpr & operator<(bool b);
MathExpr & operator==(bool b);

operator bool() {
return value_;
}

private:
T t_;
bool value_;
};


int main() {
std::cout << "(1 < 2 < 2) yields " <<
(1 < 2 < 2) << std::endl;
std::cout << "(MathExpr<int>()>> 1 < 2 < 2) yields " <<
(MathExpr<int>()>> 1 < 2 < 2) << std::endl;
std::cout << "(-2 < -1 < 0) yields " <<
(-2 < -1 < 0) << std::endl;
std::cout << "(MathExpr<int>()>> -2 < -1 < 0) yields " <<
(MathExpr<int>()>> -2 < -1 < 0) << std::endl;
std::cout << "(-2 < -1 < true) yields " <<
(-2 < -1 < true) << std::endl;
// std::cout << "(MathExpr<int>()>> -2 < -1 < true) yields " <<
(MathExpr<int>()>> -2 < -1 < true) << std::endl;
std::cout << "(2 == 2 == 2) yields " <<
(2 == 2 == 2) << std::endl;
std::cout << "(MathExpr<int>()>> 2 == 2 == 2) yields " <<
(MathExpr<int>()>> 2 == 2 == 2) << std::endl;
std::cout << "(2 == 2 == 1) yields " <<
(2 == 2 == 1) << std::endl;
std::cout << "(MathExpr<int>()>> 2 == 2 == 1) yields " <<
(MathExpr<int>()>> 2 == 2 == 1) << std::endl;
std::cout << "(-2 == -2 < 0) yields " <<
(-2 == -2 < 0) << std::endl;
// std::cout << "(MathExpr<int>()>> -2 == -2 < 0) yields " <<
(MathExpr<int>()>> -2 == -2 < 0) << std::endl;
// the below does work, but (-2 == -2) && (-2 < 0) may be better
std::cout << "((MathExpr<int>()>> -2 == -2) < 0) yields " <<
((MathExpr<int>()>> -2 == -2) < 0) << std::endl;
return EXIT_SUCCESS;
}


The commented lines won't compile. The first one is the example I gave
in another post: if someone wrote

int x, y;
bool b;

if (x == y == b) { ... }

and some maintenance programmer inadvertantly converted it to

if (MathExpr<int>()>> x == y == b)

then he'd get a compile-time error instead of an incorrect result.

In the second case, the problem is that operator < has a higher
precedence than operator ==, so that the (-2 < 0) subexpression is
evaluated first, potentially yielding an incorrect result if it were
allowed. Again a compile-time error is triggered however.


I started off with an alternative syntax, which allowed writing

if (math_expr(1) < 2 < 2) { ... }

or

if (math_expr<int>(1) < 2 < 2) { ... }

I'm not sure which I like better.

Lourens

Seungbeom Kim

unread,
Dec 10, 2006, 8:47:42 PM12/10/06
to
Vidar Hasfjord wrote:
>
> I think n-ary operators is a too irregular addition to the language. My
> suggestion is to use (the proposed) variadic template functions with
> much of the same convenience:
>
> lt (a, b, c) // == (a < b && b < c)
> gt (a, b, c) // == lt (c, b, a)
> le (a, b, c) // == !lt (c, b, a)
> ge (a, b, c) // == !lt (a, b, c)
> eq (a, b, c) // == (a == b && b == c)
> ne (a, b, c) // == !eq (a, b, c)

It's not very obvious whether ne(a, b, c) should mean !(a == b && b ==
c) or (a != b && b != c) or (a != b && b != c && c != a). One could
argue, for example, that ne(...) should mean everything is not equal to
one another because eq(...) means everything is equal.

On the other hand, even before standardisation I could define the
semantics I needed, write a library and start using it, but just
spelling out what I want (a <= b && b < c, for example) is so easy that
I'm sure I wouldn't go as far as to do the other thing. :)

--
Seungbeom Kim

Yechezkel Mett

unread,
Dec 11, 2006, 8:55:59 AM12/11/06
to
kwikius wrote:
> Vidar Hasfjord wrote:
>> Kevin Hall wrote:
>>> The language itself cannot be fixed without breaking programs written a
>>> long time ago. Fortunately, a standard library component is being
>>> developed. From what I understand, there will be a std::tr2::array
>>> (based on boost::array) included in the next technical report.
>> Yes, I was aware of tr1::array, and I should have mentioned it. (It is
>> actually in TR1, see section 6.2.) It is actually neat how a
>> fundamental flaw can be replaced by a UDT that fully works the way the
>> native type should have done;
>
> It doesnt because you need to explicitly specify the size:
>
> std::tr1::array<vect<value_type> >,??? > ={
> vect<value_type>(0,0),
> vect<value_type>(1,0),
> vect<value_type>(1,1),
> vect<value_type>(0,1),
...

> vect<value_type>(0,0),
> vect<value_type>(2,0),
> vect<value_type>(2,1),
> vect<value_type>(0,1)
> };

auto a = make_array<vect<value_type>>(


vect<value_type>(0,0),
vect<value_type>(1,0),
vect<value_type>(1,1),
vect<value_type>(0,1),

...


vect<value_type>(0,0),
vect<value_type>(2,0),
vect<value_type>(2,1),
vect<value_type>(0,1)

);

with the following definition for make_array:

template<class T, class... Args>
struct ArrayTypeCalculator
{
static const size_t size = 1 + ArrayTypeCalculator<Args...>::size;
typedef std::tr1::array<T, size> type;
};
template<class T>
struct ArrayTypeCalculator<T>
{
static const size_t size = 1;
typedef std::tr1::array<T, 1> type;
};

template<class OutIter, class Arg0, class... Args>
inline void Copy(OutIter out, Arg0 arg0, Args... args)
{
*out = arg0;
Copy(++out, args...);
}
template<class OutIter>
inline void Copy(OutIter out)
{}

template<class T, typename... Args>
inline typename ArrayTypeCalculator<T, Args...>::type
make_array(T arg0, Args... args)
{
ArrayTypeCalculator<T, Args...>::type result = {arg0};
Copy(&result[0] + 1, args...);
};

Of course, this does rely on a few features planned for C++0x.

Yechezkel Mett

peter koch larsen

unread,
Dec 11, 2006, 8:57:51 AM12/11/06
to

Vidar Hasfjord skrev:

> peter koch larsen wrote:
> >>> Dynamic arrays are handled in C++ by std::vector, as you probably
know,
> >>> which of course is built on the static array.
>
> > That is not quite correct. There is no static array hidden inside a
> > std::vector and there can not be one in a conforming implementation
> > (nor can there be any new T[]).
>
> You're right; my language was poor. I didn't mean that vector has a
> static array as a *part*. You cannot declare a static array with a size
> determined at run-time, so you must use pointers in the implementation
> of vector.
>
> What I was thinking of was the relationship between pointers and arrays
> in C/C++. Conceptually a pointer can be thought of as an iterator over
> a static array covering the whole address space.

Right. We seem to agree, and in fact I considered mentioning pointer
arithmetic as necesarry for std::vector (although less would do fine -
namely lossless conversion from pointers to int and back). Just as long
as you rephrase "whole address space" to be only the memory you
allocated.

/Peter

Vidar Hasfjord

unread,
Dec 11, 2006, 8:53:22 PM12/11/06
to
kwikius wrote:
> Vidar Hasfjord wrote:
> > It is actually neat how a fundamental flaw can be replaced by a
> > UDT that fully works the way the native type should have done;
>
> It doesnt because you need to explicitly specify the size:
>
> std::tr1::array<vect<value_type> >,??? > ={
> [...]
> };

You're right. But hopefully C++0x will fix that:

typedef vect <value_type> T;
std::array <T, auto> a = {...};

Using auto to deduce static information is nice and regular.

AFAIKT the size is deducible and the above should work. Otherwise
Yechezkel Mett's solution using a variadic template function is an
option.

Yechezkel Mett

unread,
Dec 12, 2006, 5:18:08 AM12/12/06
to
Vidar Hasfjord wrote:
> kwikius wrote:
>> Vidar Hasfjord wrote:
>>> It is actually neat how a fundamental flaw can be replaced by a
>>> UDT that fully works the way the native type should have done;
>> It doesnt because you need to explicitly specify the size:
>>
>> std::tr1::array<vect<value_type> >,??? > ={
>> [...]
>> };
>
> You're right. But hopefully C++0x will fix that:
>
> typedef vect <value_type> T;
> std::array <T, auto> a = {...};
>
> Using auto to deduce static information is nice and regular.
>
> AFAIKT the size is deducible and the above should work.

As far as I understand, that won't work. Quote from N2100:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2100.pdf
----
We do not propose to allow an "unqualified initializer list" to be used
as an initializer for a variable declared auto or a template argument.
----

Yechezkel Mett

Vidar Hasfjord

unread,
Dec 12, 2006, 12:38:26 PM12/12/06
to
Yechezkel Mett wrote:

> Vidar Hasfjord wrote:
> > std::array <T, auto> a = {...};
> As far as I understand, that won't work. Quote from N2100:
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2100.pdf
> ----
> We do not propose to allow an "unqualified initializer list" to be used
> as an initializer for a variable declared auto or a template argument.

My understanding is that this only applies to the case when the whole
variable is declared auto:

auto a = {1, "Hi!"}; // What is the type of a?
template <class T> void foo (T a);
foo ({1, "Hi!"}); // What is the type of a?
typeid ({1, "Hi!"}); // Not allowed, list has no type.

AFAIKT this is because the proposal doesn't give the initializer list a
proper type. It only describes a mechanical transformation to the
std::initializer_list <T> type. The latter is incapable of describing a
general initializer list. My view is that this is a weakness of the
proposal. I would prefer a proper type for the initializer list, i.e. a
tuple type. For example,

typedef std::tuple <int, const char [4]> A;
typeid ({1, "Hi!"}) == typeid (A);
auto a = {1, "Hi!"}; // allowed, typeid (a) == typeid (A)
foo ({1, "Hi"}); // calls foo <A> (A);

A tuple should implicitly convert to an array when elements allow an
unambiguous conversion. For example,

double b [3] = {1, 2.0, 3.0f}; // implicitly converted

This is first converted to (tuple <double, double, double>) then to
(double [3]).

Yechezkel Mett

unread,
Dec 13, 2006, 8:27:12 AM12/13/06
to
Vidar Hasfjord wrote:
> Yechezkel Mett wrote:
>> Vidar Hasfjord wrote:
>>> std::array <T, auto> a = {...};
>> As far as I understand, that won't work. Quote from N2100:
>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2100.pdf
>> ----
>> We do not propose to allow an "unqualified initializer list" to be used
>> as an initializer for a variable declared auto or a template argument.
>
> My understanding is that this only applies to the case when the whole
> variable is declared auto:
>
> auto a = {1, "Hi!"}; // What is the type of a?
> template <class T> void foo (T a);
> foo ({1, "Hi!"}); // What is the type of a?
> typeid ({1, "Hi!"}); // Not allowed, list has no type.

I don't think so. auto is defined in terms of template parameter
deduction. In the current language a deduced context inhibits any
conversions, and I think the construction of an object from an
initializer list is similar. Note that allowing deduction here would
require choosing a constructor from an overload set including all
constructors of all types that could be deduced, in this case
std::array<T, n> for all n. This isn't done in the current language, and
I don't think it is the intent in the proposal.

Yechezkel Mett

Vidar Hasfjord

unread,
Dec 14, 2006, 7:58:07 AM12/14/06
to
Yechezkel Mett wrote:
> Vidar Hasfjord wrote:
>> array <T, auto> = {...};

> I don't think so. auto is defined in terms of template parameter
> deduction. In the current language a deduced context inhibits any
> conversions, and I think the construction of an object from an
> initializer list is similar.

You're right; I mistakenly thought of the right hand as of type (array
<T, N>), in which case no conversion would be necessary. But of course,
the right hand side has no proper type (the proposal is to implicitly
convert its undetermined type to initializer_list <T>). This means a
minor (but cumbersome) rewrite is necessary:

initializer_list <T> il = {...};
array <T, il.size ()> a = il; // il.size () is a constexpr

This looks awkward. This shows that the initializer_list type is
inadequate. If it at least was parameterized on size, then we could
write a simple generic factory function with an initializer list as a
parameter:

template <class T, size_t N>

array <T, N> make_array (const initializer_list <T, N>&);

auto a = make_array ({...});

That would require sequence constructors to be templated, something
that the current proposal tries to avoid. But I question the wisdom in
that design choice.

0 new messages