C++20 Improved the For-Loop Syntax
lzon.ca
lzon.ca
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";
}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.
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.
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);
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.
Even in c++20, you could use format() to do most of what you'd want from it.
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.
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?
iostreams hardly played a role in all C++ GUI frameworks and for object serialisation, precision flags were seldom used.
This can’t be further from truth. C++ is essentially Frankenstein’s monster.
If anything it is more of a Chimera
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.
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()));
}
}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
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...
import std.stdio;
string[9] vec = [
"the", "quick", "brown", "fox",
"jumped", "over", "the", "lazy", "dog"
];
void main()
{
foreach (i, s; vec)
writeln(i, ": ", s);
} 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.
It's nice because it makes the syntax for arrays and ranges interchangeable, making for easy refactoring.
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"; 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. 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?I am not counting characters, especially when AI completes typing for me.
for (auto [i, v] : std::views::enumerate(vec)) {
std::cout << i << ": " << v << '\n';
}
This is how you'd do it.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`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.
for<template> [auto&&&&]:::static (std::for_enumeration_initializer &&⁢ @auto: {{const signed&&&&& i const}}) { } 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.
> 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.
foreach (i, int, 10, vector(10, int, 1, 2, 3, 3, 4, 5))
println(*i);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.
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.
here you go: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
Reminds me of Good Parts, Bad Parts meme of JS.
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.
The home page of that link isn't only for C++.