25 comments

[ 4.6 ms ] story [ 46.5 ms ] thread
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);

The enumerate is a better solution than the one in the blog post.
This is the way I've been doing it and with less hiccups.
std::views::enumerate is a C++23 feature, not C++20.
> 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.

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.
> 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

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
> 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.

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.
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.
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);
    }
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.
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";
     }

  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.

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.
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.
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.
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.
What a strange example, as c++ did get Pythons enumerate, but in c++23
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.

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
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.