alt.hn

7/11/2026 at 4:10:19 PM

C++20 Improved the For-Loop Syntax

https://lzon.ca/posts/tips/cpp-for-range-init/

by jpmitchell

7/15/2026 at 1:45:21 AM

Reading this, I really can't make much sense of it:

     for (int i=0; auto&& it: vec)
         cout << (++i) << ": " << it << endl;
It's certainly not obvious what's going on there at a glance.

This is at least a bit more pythonic:

     for (auto [i, it] : std::views::enumerate(vec)) {
        std::cout << i << ": " << it << "\n";
     }

by UncleOxidant

7/15/2026 at 2:33:59 AM

The part that confuses me is that they use “it” to name a variable that is not actually an iterator. That’s very bad style. I would name it “s”.

by senkora

7/15/2026 at 8:34:22 AM

No argument that it's inappopriate, but I wonder if the author works with other programming languages - in some (Groovy, Kotlin, Ruby), "it" is the implicit block parameter.

by pizza234

7/15/2026 at 4:35:12 PM

Haha I always interpreted "it" to mean literally the word "it". I didn't realize it's a short-hand form for iterator, but now I'm not so sure if that's universal or if there's a genuine split.

by Maxatar

7/15/2026 at 8:55:33 AM

It's "it" as in the pronoun, not as in "iterator".

by pwdisswordfishq

7/15/2026 at 11:18:31 AM

We call that “Larry Wall nomenclature”.

by zbentley

7/15/2026 at 9:51:03 AM

As someone who wrote a lot of C and legacy C++, I find the first form easier to understand.

The "int i=0; ... ++i" part is just your typical C-style loop. And the "auto&& it: vec" is the for-each kind of loop that dates back from C++11. The only new thing is that you can do both at the same time, which is not much of a stretch.

On the second one, I had to look up what the "std::views::enumerate" did. It is kind of obvious when you look at the code, but it is not as explicit.

And maybe most importantly, it doesn't do the same thing!

The first loop starts at one, you can tell because it is ++i, not i++. A debatable choice when it comes to readability, but you can see it right before your eyes. For the second loop, I had to, again, look up to make sure the result of "std::views::enumerate" was indeed zero-indexed (and it is, of course).

Which form you prefer depends on if you like being explicit or if you prefer abstractions. I usually prefer the former. Performance-wise, the generated code is probably very close, but the first one is likely to have an advantage on debug (unoptimized) builds.

by GuB-42

7/15/2026 at 1:47:56 AM

It's probably easier to understand if you read about the "if statement with initializer" linked from the post: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p03...

by grg0

7/15/2026 at 1:58:44 AM

Not really, I was well aware of if-with-init and I still found this quite confusing. That one actually shows what the variable is initialized to. This one doesn't.

by dataflow

7/15/2026 at 5:40:07 AM

> That one actually shows what the variable is initialized to. This one doesn't. reply

Which variable? `i` is clearly initialized to `0` and `it` doesn't have an initializer per se... it starts at the first value of the container.

It almost seems like an abuse to put 2 unrelated things in the `for(...)` but that's what they did.

by hdjrudni

7/15/2026 at 9:14:24 AM

It certainly is easy, for anyone that learnt other OOP languages like Smalltalk and Object Pascal before coming into C++, as I did back in the day.

by pjmlp

7/11/2026 at 4:36:29 PM

C++20 also has an enumerate() generator, so if you like the python syntax you can just do:

for (auto [i,v] : std::views::enumerate(vec)) std::cout << i << ": " << v << std::endl;

FWIW C++23 also has a python-like print and println:

std::println("{}: {}", i, v);

by HarHarVeryFunny

7/15/2026 at 2:32:42 AM

> std::cout << i << ": " << v << std::endl;

C++ needs to abandon iostreams. Didn't the C++ community acknowledge that it was a bad idea? In the early days of D, people did want to do a version of it for D, but I objected and currently nobody wants it.

by WalterBright

7/15/2026 at 2:35:25 AM

https://en.cppreference.com/cpp/io/print

by Matheus28

7/15/2026 at 3:24:55 AM

Thanks. Looks like it finally made C++23!

by WalterBright

7/15/2026 at 9:21:32 AM

fmt:print from fmtlib has been around for at least a decade, though having it be in the spec by default now is nice.

Even in c++20, you could use format() to do most of what you'd want from it.

by extraduder_ire

7/15/2026 at 9:16:51 AM

Some folks don't like iostreams, I keep using them, since Turbo C++ 1.0 for MS-DOS.

Although I would probably use std::print in more modern compilers.

D needs to work into its marketing, all key features that made it relevant back in 2011 with Andrei's book have now been copied even if badly, across all mainstream languages.

by pjmlp

7/15/2026 at 5:33:11 PM

> Some folks don't like iostreams

I thought it was ugly in 1987, and it hasn't improved with age. Then < > for templates made it worse.

Then there's formatted I/O:

    void IOS_precision()
    {
    cout << "\n--------------------------\n";
    cout << "Implementing ios::precision\n\n";
    cout << "Implementing ios::width";
    cout.setf(ios::fixed, ios::floatfield);
    cout.precision(2);
    cout<<3.1422;
    cout << "\n--------------------------\n";
    }
and I don't know if the problem with multithreading was resolved or not.

> copied even if badly

Any program can be written in any language. But why suffer?

by WalterBright

7/15/2026 at 5:43:44 PM

That is always given as an example of how bad it is, yet I seldom have used any kind of such formatting since 1993.

iostreams hardly played a role in all C++ GUI frameworks and for object serialisation, precision flags were seldom used.

by pjmlp

7/15/2026 at 1:46:12 AM

This is the way I've been doing it and with less hiccups.

by arikrahman

7/15/2026 at 1:48:57 AM

std::views::enumerate is a C++23 feature, not C++20.

by Maxatar

7/15/2026 at 1:36:01 AM

The enumerate is a better solution than the one in the blog post.

by BigTTYGothGF

7/15/2026 at 1:11:33 AM

> It seems to me that the C++ Standards Committee is doing a decent job maintaining the language, and introducing useful features when it makes sense to do so.

This can’t be further from truth. C++ is essentially Frankenstein’s monster.

by adityamwagh

7/15/2026 at 9:17:58 AM

All mainstream languages that survive 1.0, have a bit of Frankenstein into them.

by pjmlp

7/15/2026 at 1:43:36 AM

And the real monster was the one who created it all along!

by saghm

7/15/2026 at 1:40:34 AM

An articulate creature that hounds its creator for abandoning it?

If anything it is more of a Chimera

by gregdaniels421

7/15/2026 at 2:26:37 AM

I believe one of the main goals of whatever shadowy organizations are behind the "standards committee" is to sabotage the language to help clear the way for Rust.

If these people really have a great idea for what the future C++ should be like, then they should introduce that language all at once. Let us see their grand vision in one big package that can be embraced or rejected. This steady drip-drip-drip of half-baked features every few years is creating one hellish language.

I spend a lot of time in the Chromium source code and have seen plenty of boneheaded code, especially in places like Autofill where they seem to turn the newbies loose, but also in surprising places like V8, due to newbs using newfangled C++ "features" to do what worked perfectly well before in older idioms and was easier to read also.

Take for instance the function TryReduceFromMSB in v8/src/compiler/turboshaft/wasm-shuffle-reducer.cc -- do you see any reason that std::optional<uint8_t> max; couldn't just be a regular uint8_t? Let's name it 'max_used' also so the name is more descriptive and doesn't clobber the namespace.

by hnisnotbenign

7/15/2026 at 9:18:42 AM

The same organisations are behind Rust Foundation, and are responsible for the LLVM and GCC compilers, written in C++, rustc depends on.

by pjmlp

7/15/2026 at 5:19:45 PM

[dead]

by hnisnotbenign

7/15/2026 at 8:01:51 AM

>do you see any reason that std::optional<uint8_t> max; couldn't just be a regular uint8_t?

It's so that

    if (max)
is truthy iff max has been assigned (including the case where it's been assigned zero).

I don't know if it's possible for max to be assigned zero in this specific context, or what the correct behavior is if max==0. However, the good thing about this code is that it clearly communicates that the intended check is 'has been assigned to' rather than 'is nonzero'.

The function, for reference:

    void WasmShuffleAnalyzer::TryReduceFromMSB(OpIndex input,
                                               const Simd128ShuffleOp& shuffle,
                                               uint8_t lower_limit,
                                               uint8_t upper_limit) {
      DemandedBytes demanded = GetDemandedBytes(&shuffle);
      std::optional<uint8_t> max = {};
      for (unsigned i = 0; i < demanded.bytes(); ++i) {
        uint8_t index = shuffle.shuffle[i];
        if (index >= lower_limit && index <= upper_limit) {
          max = std::max(static_cast<uint8_t>(index % kSimd128Size),
                         max.value_or(uint8_t{0}));
        }
      }
      if (max) {
        // input can be reduced.
        TRACE("Can reduce Op %d based upon max used index: %d\n", input.id(),
              max.value());
        demanded_byte_analysis_.Add(
            input, DemandedBytes::LowFromMaxShuffleIndex(max.value()));
      }
    }

by foldr

7/11/2026 at 5:53:39 PM

The C++20 version is still clearly inferior to the Python and Lua examples because you still have to manually increment the counter in the loop body. IMO the sibling comment by HarHarVeryFunny has a much better C++ equivalent for this idiom, even if it's slightly more verbose.

by WCSTombs

7/15/2026 at 12:43:25 AM

> you still have to manually increment the counter in the loop body

It doesn't look like that to me, the ++i thing seems to be just to start printing the array from 1 (I don't know how things are in Python nowadays but I know in Lua arrays start at 1, so there's no need for something like this in there), the value of i is still increasing without telling it explicitly to do so

by Gualdrapo

7/15/2026 at 12:51:34 AM

Python is 0-indexed. In OP's example, the start parameter makes i start at 1.

Their C++17 example prints starting from 0. Probably a mistake.

If you look at the linked page for C++20[0], other types can be put in the initializer statement, so it's unlikely the loop auto-increments.

[0]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p06...

by sheept

7/15/2026 at 1:00:46 AM

So, it's just a while loop in a for loop's clothing? That's not going to be confusing for people at all...

by kbenson

7/15/2026 at 1:19:00 AM

No, it's a for loop that happens to include an unrelated variable declaration.

by comex

7/15/2026 at 2:51:26 AM

Ah, I see. The increment is so they can manually handle the included feature of the other cases, which number of the iteration you're on. Nice that it will automatically loop across a collection, annoying that they couldn't go the whole way and you need to deal with it manually.

by kbenson

7/15/2026 at 12:52:28 AM

No, the increment is necessary to keep the counter going. The choice of pre-increment versus post-increment handles the "start at 1" issue you mentioned, but it isn't auto incrementing.

by moron4hire

7/15/2026 at 1:35:32 AM

C++ should copy D's elegance:

    import std.stdio;

    string[9] vec = [ 
      "the", "quick", "brown", "fox", 
      "jumped", "over", "the", "lazy", "dog" 
    ];

    void main()
    {
      foreach (i, s; vec)
        writeln(i, ": ", s);
    }

by WalterBright

7/15/2026 at 9:21:24 AM

C++23 example,

    import std;

    using namespace std;

    int main() {
        vector vec = { 
            "the", "quick", "brown", "fox", 
            "jumped", "over", "the", "lazy", "dog" 
        };

        for (auto&& [i, it] : vec | std::views::enumerate)
            println ("{}:{}", ++i, it);
    }
As I keep mentioning, the features being copied, slowly erode D's relevance.

by pjmlp

7/15/2026 at 5:37:31 PM

If you prefer writing:

    for (auto&& [i, it] : vec | std::views::enumerate)
        println ("{}:{}", ++i, it);
instead of:

    foreach (i, s; vec)
        writeln(i, ": ", s);
well, what can I say?

by WalterBright

7/15/2026 at 9:17:17 PM

I prefer to use the option with better market share.

I am not counting characters, especially when AI completes typing for me.

by pjmlp

7/15/2026 at 7:58:53 PM

We may first need to go over what "elegant" means I guess. :)

by acehreli

7/15/2026 at 9:18:35 PM

There is elegant when writing code by hand, and when reviewing code by AI agents.

by pjmlp

7/15/2026 at 1:47:46 AM

Although D is a strongly typed language, it is very good at type inference. The `s` is inferred as `string`, and `i` as `size_t`.

by WalterBright

7/15/2026 at 2:25:01 AM

Go is similar:

  package main

  import "fmt"

  var vec = []string{"the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"}

  func main() {
    for i, s := range vec {
      fmt.Printf("%d: %s\n", i, s)
    }
  }
Using `fmt.Println(i, ":", s)` inserts spaces on both sides of the colon, so the output is not quite right.

I find Go's `range` syntax easier to read than D's `(i, s; vec)` -- although generator functions that work with `range` have unaesthetic return types.

by entrope

7/15/2026 at 1:35:24 PM

I find Go’s version worse in that it does not allow you to elide the index (which is not needed most of the time). You have to write `for _, s := range vec`, as `for s := range vec` makes s the index.

by xigoi

7/15/2026 at 5:39:57 PM

To clarify,

    foreach (s; vec)
works if you don't need the index. To be able to modify s in place:

    foreach (ref s; vec) s = "replacement";

by WalterBright

7/15/2026 at 9:34:53 PM

I was talking about Go. I know you don’t need the index in D.

by xigoi

7/15/2026 at 2:29:17 AM

D has several usages for `foreach` depending on the number and type of its arguments. In this case, it sees that `vec` is an array, and so constructs a loop over the array. If `vec` was a range, the loop will be constructed as a loop over the array.

It's nice because it makes the syntax for arrays and ranges interchangeable, making for easy refactoring.

by WalterBright

7/15/2026 at 11:28:30 AM

Go is similar: `for/range` has always worked on arrays, slices, maps, strings and channels, and they extended it to integers in version 1.22 and to user-defined generators in version 1.23. It generates one or two outputs depending on the type of the RHS, but one can assign the index value to _ to ignore it. Among standard types, only channels and integers generate one output; containers (including strings) give an index or key and a value.

by entrope

7/15/2026 at 5:42:40 AM

I don't like that semi-colon. AFAICT `i, s;` is not an independent statement. `i, s: vec` would have been fine, but `;` ?!?! madness.

by hdjrudni

7/15/2026 at 8:01:29 PM

Well, others might have said `i, s:` is not an independent label.

by acehreli

7/15/2026 at 2:00:06 AM

As someone who likes and uses modern C++ this seems like a very marginal improvement. I see the use case but it seems rather unimportant.

by jandrewrogers

7/15/2026 at 1:36:47 AM

Given both Lua and python use a enumerator, the C++ example isn't really the correct equivalent.

  for (auto [i, v] : std::views::enumerate(vec)) {
      std::cout << i << ": " << v << '\n';
  }
This is how you'd do it.

by HeavyStorm

7/15/2026 at 2:25:48 AM

True, but this is C++23, while the post is about C++20.

by ziotom78

7/15/2026 at 9:12:37 AM

Even more modern, we are getting C++26 nowadays.

CTADs for vector (no need for explicit string), ranges enumeration, and range-based for-loop with structured types

    int main() {
        vector vec = { 
            "the", "quick", "brown", "fox", 
            "jumped", "over", "the", "lazy", "dog" 
        };

        for (auto&& [i, it] : vec | std::views::enumerate)
            println ("{}:{}", ++i, it);
    }
Compiler explorer example, https://cpp.godbolt.org/z/3r8rPdjbG

by pjmlp

7/15/2026 at 10:35:05 AM

I'm super confused. In your example, ++i is equivalent to (i+1) and the loop updates i itself. But with the syntax in the blogpost, i is only initialized, and it is the ++i that updates the value?

by Bimos

7/15/2026 at 11:13:50 AM

Copy paste error, too late to update the comment, it should be a plain i.

by pjmlp

7/15/2026 at 11:07:24 AM

Yes. You don’t sound that confused?

by spider-mario

7/15/2026 at 1:21:04 AM

Only people who saw nothing bad in passing pairs .begin(), .end() in tons of places in c++ for like 30 years can say that thing like ‘auto&&’ improves anything.

by galkk

7/15/2026 at 9:34:16 AM

and completely unnecessary in that example, it's a string, type string, makes it way easier to understand

by lentil_soup

7/15/2026 at 2:43:52 AM

What exactly is your complaint with it?

`auto&&` has been standard syntax in C++ for going on 15 years now and has a very clear, irreplaceable meaning - a type-deduced forward reference - to any remotely competent developer.

by Tadpole9181

7/15/2026 at 9:11:34 AM

I think C++ needs more of `:`, `::` and maybe new operators `&&&` and `&&&&`.

by HackerThemAll

7/15/2026 at 1:42:20 PM

C++32 will look like

  for<template> [auto&&&&]:::static (std::for_enumeration_initializer &&&it; @auto: {{const signed&&&&& i const}}) { }

by xigoi

7/15/2026 at 2:27:50 PM

the looping facilities in most languages suck. We are at a position where iteration is either "increment or decrement numbers", or "go through this collection". Everything else means degrading performance. In some languages (Python, ruby) the choice is between the comfortable way to iterate through a collection (for a in xyz:), or the fast way (while). It is just stupid.

by bjoli

7/15/2026 at 1:47:44 AM

  1> (mapdo (op put-line `@1: @2`) '#"how now brown cow" 0)
  how: 0
  now: 1
  brown: 2
  cow: 3
  nil
mapdo: a mapping function for side effects of calling the function, not calculating a result, like map does.

op: produce a lambda expression out of an expression in which @1, @2, ... explicitly indicate the insertion of positional arguments, which are implicitly collected and become the parameter list of the lambda.

`...`: quasistring syntax: supports @ notations for interpolating. The @1, @2 elements of op do not require a double @@ inside a quasistring.

put-line: ordinary function to put a string to a stream (standard output by default) followed by newline. The lambda generated by op contains a (put-line ...) expression as its body, with @1 and @2 transformed into references to to generated, unique parameter names.

#"...": string list literal: contents are broken on whitespace and denote a list of strings #"foo bar" -> ("foo" "bar"). Requires ' quote in front to be quoted literally, and not evaluated as a compound expression applying the argument "bar" to the operator "foo". Yes, there is a #`...` quasi string list for templating over this.

nil: the value returned by mapdo after the side effects, printed by the REPL, not part of the output.

0: ordinary integer zero. But endowed with the power of being iterable. Where an iterable thing is required, 0 denotes the whole numbers 0, 1, 2, ... Similarly, 42 denotes 42, 43, ...

These are some of the ingredients produced by my one-member research programme into nicer Lisp coding.

  2> (map (ret `@1: @2`) '#"how now brown cow" 0)
  ("how: 0" "now: 1" "brown: 2" "cow: 3")
map: take tuples by iterating over argument iterables in parallel, pass them to a function to project each tuple to a value, then return a list of values.

ret: cousin of op built on the same framework as op. Used for turning an expression into a lambda, when the expression isn't a compound form with an obvious operator. To turn (foo bar) into a lamdbda with op we use (op foo bar). But what if we have a simple variable x and want (lambda () x)? (op x) is not right, it means (lambda () (x)). (ret x) provides the sugar. Here, it lets us spin up a two-argument function that evaluates a quasistring.

by kazinator

7/15/2026 at 12:39:55 AM

The way this website shows the programming languages is odd. They're blue and slanted and if you hover your mouse cursor over them they have a color transition, so you'd think they are links - and yet you click on them and nothing happens

by Gualdrapo

7/15/2026 at 1:49:15 AM

Give me the explicit C syntax any day over this monstrosity. I refuse to write anything other than C++98, the last version of C++ that built on C without trying to turn it into a completely different language.

by drnick1

7/15/2026 at 9:23:29 AM

So K&R C it is, nothing of that C23 crazy stuff.

by pjmlp

7/15/2026 at 1:22:49 AM

I am so happy I haven't written a line of C++ in like 15 years. Absolutely disgusting language. Every time I look up one of their new standards, I'm like how does anyone keep all of this in their head (usually on top of stuff like boost, etc.)? No wonder LLMs are a thing.

by dvt

7/15/2026 at 1:47:40 AM

C++ is a decades-long mistake that spawned other misguided directions in the entire software industry. We're still trying to undo many of the bad ideas, but I'm afraid even Rust has too much of C++ in it, not only in terms of syntax but mentality and culture. LLMs are making things worse by automatically generating code in such bloated languages, where people have less need to even look at the horrific spaghetti inside. The whole thing needs to be reconsidered from the ground up, from first principles, by actual creative thinking human beings with lessons of hindsight.

> Gall's Law: A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system.

by lioeters

7/15/2026 at 1:54:45 AM

I guess I am officially old and no longer know what the cool "it" is any more. The C++17 example seems much more clear to me. It is more typing, but so what? It is not that much more typing, and it looks like code that people have written for 40 years.

by hackthemack

7/15/2026 at 2:36:44 AM

What a strange example, as c++ did get Pythons enumerate, but in c++23

by on_the_train

7/15/2026 at 6:20:47 AM

You can do this right now with an iterator adaptor and some range trickery. The c++ range based for loop already enables the same behavior as python. The only thing missing is a standard enumerate function.

This is a Fine addition to c++ due to how c++ uses lifetimes for resource management but the syntax is convoluted and the claim that this is intended to enable enumeration is... Just silly.

by anon291

7/15/2026 at 12:31:01 AM

Just what C++ needs, more bloat lol.

by fourseventy

7/15/2026 at 12:38:56 AM

C++ is probably the only language that needs a strong style guide, to define a subset you are allowed to use in the project.

Linus Torwalds famously said that subset is zero. You're not allowed to use C++ in Linux, a wise move.

Here's Google's: https://google.github.io/styleguide/cppguide.html Search for "do not use" and you'll find plenty of hits.

by petilon

7/15/2026 at 1:59:58 AM

And it's not because he's cranky or something, he was holding out for something worthy to possibly use instead of C. Turned out to be Rust.

by bigcityslider

7/15/2026 at 10:09:43 PM

> And it's not because he's cranky or something, he was holding out for something worthy to possibly use instead of C.

Good point.

> Turned out to be Rust.

He was evaluating C++ in the 90's, you make it sound like he recently decided to write Rust instead of C. The reality is that Rust is allowed in limited ways into _some parts_ of the kernel codebase. Rust got so much further than C++ ever did, that is true.

by leecommamichael

7/15/2026 at 9:24:56 AM

Java, Python, C#, Common Lisp, Perl, also deserve guides.

The home page of that link isn't only for C++.

by pjmlp

7/15/2026 at 8:34:03 AM

Perl is another example. It has a dedicated linter to detect the myriad ways of using it wrong.

by rjh29

7/15/2026 at 2:33:18 AM

This is not an improvement. In fact it makes the code harder to understand. The way it's written makes people less familiar with this syntax think `int i = 0` and `auto &&it : vec` are two separate statements.

by vianchen