7 ms·
C++17 creates a practical use of the backward array index operator
- tpoacher 4y agoBefore reading the article, I thought he was talking about negative indices. 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.
- cornstalks 4y ago> Starting in C++17, a[b] always evaluates a before evaluating b. Okay, I'll bite. Why did C++17 specify this?
- olliej 4y agoBetter question: why did C and C++ not define this as left to right (the answer is perf claims decades ago, that are not really valid now, and questionable even then). 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.
- TremendousJudge 4y agoHalf of the new features of "modern" C++ editions leave me agreeing with the change but wondering what they were thinking before. Another example: std::map::contains was introduced in C++20; why did they go decades thinking it wasn't necessary to have such an operator (instead providing only count() even though a map has unique keys), and why did they change their minds only now? Did the old guard literally die off or something?
- olliej 4y agoThe historical lack of sequencing is because they were unsequenced in C, and C++ thus inherited it. 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.
- TremendousJudge 4y agoI get it, but then, why change it now? (or 6 years ago I guess)
- olliej 4y agoSorry, I had an unsaved edit above. It seems the resistance to removing unnecessary UB has reduced over the years.
- PaulDavisThe1st 4y agostd::map::find (item) != map.end() <=> std::map::contain (item)
- zimpenfish 4y agoI think the problem is that with `std::map::find(item)` you get the value back but you don't with `std::map::contain(item)` - which means a second lookup to actually retrieve the value, no?
- tubs 4y agoProblem with contains is we are now going to see more code like 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.
- JonChesterfield 4y ago
- deleted 4y ago[deleted]
- eklitzke 4y agoBecause otherwise the evaluation order of the following construct is undefined, and you can get different results if you compile your code with different compilers: 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.
- addaon 4y agoBut both C and C++ are comfortable with *(foo() + bar()) having undefined evaluation order, so foo()[bar()] potentially having order-dependent results is clearly not sufficient reason for this decision...
- Dylan16807 4y ago> 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.
- ynik 4y agoTo allow for chaining. Consider a statement like: 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).
- olliej 4y agoWhat this is talking about is the behavior of [] in C and C++. In these 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.
- jbverschoor 4y agoI’ve never seen that before. Luckily
- olliej 4y agoHe was also a tutor/TA and I think mostly used it to explain how pointers vs. indexing worked. Or possibly to traumatize students :D
- int_19h 4y agoIt is extremely rare even in ancient C code. The only reason why it exists in C at all is because rewriting a[i] as *(a+i) was inherited by C from B; the latter only has a single machine word type for everything, so there was no way to define it such that a[i] would be legal but i[a] would be illegal in B.
- HelloNurse 4y agoIn the competing array indexing syntax *(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.
- omoikane 4y agoBackward array index always had a use in the code golf context, like this: 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.
- zeusk 4y agoIf saving bytes is the goal, why not > int a[]={0,1,2}; > int y = a[1];
- pritambaral 4y agoIt's a demonstration, not real code. The first block of code is prologue, not main body.
- gumby 4y agoIf you care, just use the + operator which is unambiguous.
- lvkv 4y agoI’ve always thought of the array index operator: 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.
- mhh__ 4y agoThat's exactly what it is in C
- JTyQZSnP3cQGa8B 4y agoI haven't used pointer arithmetic for a long time but the "value" of the index depends on the type, doesn't it? For a char[], it's (a + index), but for an int[] it should be (a + 4*index), or (a + sizeof(type)*index). 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.
- FreeFull 4y agoWhen you do `*(pointer + 1)`, it'll actually increment the address of the pointer by `sizeof(*pointer)`, so it indeed works the same as indexing `pointer[1]`
- addaon 4y agoThe key point of the article here is that this is no longer strict syntactic sugar in C++ as of C++17. The addition operator, like many operators and all function calls, does not specify the order that its arguments are evaluated. In C, and in C++ prior to C++14, neither did the indexing operator; but as of C++17, the order is fully defined (`a` first in `a[i]`).
- TylerGlaiel 4y agoplease... just explicitly calculate index() first on its own line if the order matters like this...
- orangepanda 4y agoWould compilers optimise the unnecessary variable away?
- devnull3 4y ago> Astound your friends! Confuse your enemies! ... more like your friends will curse you and your enemies will watch with glee that you are using C++
- bfrog 4y agoAh yes, another rule to try and remember while writing a reviewing c++
- addaon 4y agoThis was previously an undefined order. It is now defined. Any code that you would have reviewed previously requires no additional knowledge to review now. Any code that is depending on this new C++17 defined behavior is either pushing the cleverness ceiling, or is going to document the dependency.
- pjmlp 4y agoAnyone not using something like Sonar in their pipelines is really doing themselves a disservice, even with languages considered less bloated like Python, Java, C#, I cannot keep up with all the rules after circa 25 years of ecosystem evolution (less for C#).
- xorvoid 4y agoOh C++… The level of excitement over pointless triviality you generate never ceases to amaze me. 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…