C++17 creates a practical use of the backward array index operator
devblogs.microsoft.com
devblogs.microsoft.com
int a[3] = {0, 1, 2};
int *p = a;
int **q = &p;
int x = (*q)[1]; // Read a[1]
int y = 1[*q]; // Same, but saves 2 bytes
Code golf considerations aren't always related to practicality, of course.> int a[]={0,1,2};
> int y = a[1];
a[index]
as syntactic sugar for the pointer arithmetic: *(a + index)
From this point of view, the existence of a “backwards” index operator makes sense; the arithmetic evaluates to the same address.I have never understood how the compiler is supposed to know that it's (a + 4*index) and not (index + 4*a) which gives different results. Maybe it's based on the type itself and the computation is done by multiplying the "integral" index with it's size, and adding the "pointer" if there is one.
But even this can give wrong values if you're on an embedded platform where you can store your pointers as integers, size_t, or simple defines without a type.
If anyone knows where I can find the explanation in the standard, I've been wondering about it for at least 20 years...
Edit: it seems that a simple example gives the solution: (void*)[void*] is invalid, and int[int] is invalid too, therefore the compiler will get the pointer as the address, and the integral type as the index. I feel stupid for not testing this earlier.
... more like your friends will curse you and your enemies will watch with glee that you are using C++
x[y]
and y[x]
are the same. I was first introduced to this by a friend of mine many many years ago (because I'm an old) as for (i = 0; etc)
putc(i["hello world"])
or similar nonsense.The C++ change that makes this matter is that apparently pre c++17 doesn't enforce sequencing such that expression1[expression2] doesn't require expression1 be evaluated before expression2. C++17 does actually fix the sequencing to be left to right, so now expression1[expression2] will always evaluate as expression1;expression2 and expression2[expression1] will always evaluated as expression2;expression1 without depending on UB.
*(a+b)
there is the same evaluation order issue, and they should have the same behaviour: is a evaluated before b or the compiler is allowed to choose an order? Clearly it depends on the rules for a+b
which are more important than the special case of array indexing.Some nerds somewhere are getting all giddy about this silliness when it would just be objectively better to not have this silly quirk in the first place and to write it like a sane person who recognizes the great social benefits of maximizing understanding:
auto idx = index(); return p[idx];
Clever generally just means “bad”. Why people get so excited about it mystifies me…
Okay, I'll bite. Why did C++17 specify this?
The example
a[b]
doesn't really exhibit the unexpected, but as I understand it f()->a[b()]
could evaluate as temp = b()
f()->a[temp]
which it seems reasonable to consider unexpected. The lack of left to right sequencing here seems not-dissimilar to the lack of sequencing in call arguments, which was also realized to be a needless footgun and fixed.And yes, it does seem that there were a lot more people hell bent on resisting correcting unnecessary UB in the past than today, but they’re still around.
std::map::find (item) != map.end() <=> std::map::contain (item) if (map.contains(foo)) {
bob(map[foo]);
}
over the (vastly uglier) more efficient: if (auto it = map.find(foo); it != map.end()) {
bob(*it);
}
Of course languages like C# manage this in a more elegant way with out parameters declarable at the call site. if let Some(x) = map.get(foo) {
bob(x)
}
One subtle but noteworthy thing is that the idiom only mentions map by name once, which is nice if you have a very long map name, and prevents copy paste bugs where you only update one of the two mentions. for (x <- map.get(foo)) {
bob(x)
}
// or
map.get(foo).foreach(bob)
// or
map.get(foo) match {
case Some(x) => bob(x)
} foo()[bar()];
In nearly all cases it won't matter, but it can matter if foo() and bar() have some kind of shared state (or affect each other's return values, but seriously don't do that). In general it's better to have a defined evaluation order unless there's some compelling reason not to.That is not the stance C++ has taken in the past, so the question of why they changed it is still unanswered.
a.f(foo(1)).g(foo(2)).arr[foo(3)].h(foo(4));
In C++14, the foo() calls happened in unspecified order.
C++17 guarantees that "a(b)" and "a[b]" evaluate "a" first, which effectively means the foo() calls will now happen in order (1 to 4).I was reading K&R the other day and spotted the bit where they mention c supports negative indices and gave an example. My mind was blown.