Arrays of Arrays (2009)(ericlippert.com) |
Arrays of Arrays (2009)(ericlippert.com) |
Because of the C "comma operator". In C, the comma operator evaluates both operands and discards the first result. It's an ancient hack used mostly with preprocessor defines.
In C and C++,
foo(a,b)
is a call with two arguments. But foo[a,b]
invokes the comma operator and presents one value to "[]" You have to dig through the C++ specification to find this.So I asked the question, could anyone find an example of the comma operator used in C or C++ inside square brackets in any production code. I couldn't. I got someone at IBM Almaden to search IBM's code archive and they couldn't. But there was the concern that somewhere, someone had used that misfeature for something.
So, no 2D array syntax for you.
You might've asked operator[] to take multiple arguments separated by semicolon, so:
foo[a;b]
which is at least unambiguous. Maybe it would have been less offensive.> So I asked the question, could anyone find an example of the comma operator used in C or C++ inside square brackets in any production code.
If you're still referring to operator overloading, there's things like Boost.Assign and Boost.Phoenix, but if you're referring to use as a sequence operator, i.e. where a[b,c] could potentially be b,a[c] except for the different sequence point, it doesn't surprise me you had a hard time finding an example: I've only ever seen people like Arthur do that (people writing APL in C)
Multidimensional array discussions tended to dissolve into bikeshedding. Some people wanted the ability to store an array as either transpose, that is, by row or by column. Then that had to be extended to N dimensions. That added a lot of complexity to support a rare use case. Then the proposal got so complex it was shelved. All this is in old USENET comp.lang.c++, if that hasn't been lost yet.
Sounds like how the semicolon operator works in modern languages, like Rust.
fn main() {
println!("ok"; "Hello, world!");
}Unpack the "v" version, look at yals.c. You will see there the following:
#define PUSH(S,E) \
do { \
if (FULL(S)) ENLARGE (S); \
*(S).top++ = (E); \
} while (0)
#define POP(S) \
(assert (!EMPTY (S)), *--(S).top)
#define TOP(S) \
(assert (!EMPTY (S)), (S).top[-1])
#define PEEK(S,P) \
(assert ((P) < COUNT(S)), (S).start[(P)])
#define POKE(S,P,E) \
do { assert ((P) < COUNT(S)); (S).start[(P)] = (E); } while (0)
These are definitions for typed dynamic arrays using C macros. Very useful.As you can see, there's a couple of assert()'s before actual computation of the address to fetch.
I hope I proved you wrong.
I don't see where you did - Animats said he couldn't find examples of the comma operator inside square brackets, not that he couldn't see it being used at all.
- compiler can break it apart so foo[x] is evaluated once for many ys just like any other function
- you can make types that are unaware of their rank eg vector doesn’t know if it’s a 1D vector of int or a 2D vector of vectors
- “seamlessly” handles jagged arrays
So what you've just described is basically Currying, or partial function evaluation. It's a bit like defining "al(i)" as array lookup for an index 'i' instead of "[i]". Then the following are equivalent:
something[i][j]
something.al(i).al(j)
What people also want are: something[i,j]
something.al(i,j)
In languages like Haskell, every function can always be evaluated argument-by-argument, with no special effort by the programmer. The compiler takes care of generating the code for the various intermediate call forms. In languages like C++, it would be a significant hassle to create all of the various partially-evaluated wrapper types. [,][]int crazy;
It reads "2-dimensional array of array of int" in L-to-R order.Similarly, the type "pointer-to-int" should be "*p" or equivalent.
Unfortunately, history/tradition as well parsing problems make the "sane" solution difficult. One solution is the Pascal approach putting the type after the variable being declared, with a colon between:
crazy : [,][]int;Its native performance inside GLAS implementation is something to shout about and even comparable to OpenBLAS that is currently being used by Julia and Matlab [2][3].
[1]http://docs.algorithm.dlang.io/latest/mir_ndslice.html
[2]http://blog.mir.dlang.io/glas/benchmark/openblas/2016/09/23/...
int* a, b;
And think that this constructs two pointers-to-integer. In fact, it constructs one pointer and one plain integer. A better (equivalent) way to write it is int *a, b;
You're not declaring variables of type int*
You're saying that b is an int and *a
Is an int, where the asterisk means "the dereferenced a"! int a = 0;
int& x = a, y = a;
declares x to be a reference, but y an independent variable. Similarly, a template parametrised by a variadic pack of function pointers is declared thus: template <void (*...x)(void)>
It’s a good thing I don’t work in C++ too often, because my forehead would have probably sustained noticeable injury from all the facepalming each time I see ‘int& x’ instead of ‘int &x’ and ‘typename... T’ instead of ‘typename ...T’.