back

by matt_d·12y ago·view on hn ↗
Out of curiosity, how about making `const` the default and requiring `mutable` for mutation?

There's already a Standard precedent in form of C++11 lambdas -- and const-by-default has some technical niceties (perhaps simplicity, too -- arguably making this the default removes some complexity from the language from the beginner's point of view, e.g., by preventing accidental mutation) that may make this worth it.

// I see that you addressed the constness in A17, but I believe allowing the `mutable` opt-in, as in the lambdas, takes care of the "limiting" aspect; "confusing", OTOH, is a matter of taste -- after all, `std::for_each` operates on InputIterators as well (thus, perhaps the similarity with a non-modifying nature[1] of a Standard Library analog is arguably preferable from the consistency / least-surprise-principle point of view?), and, again, lambdas also have constness by default.

Thoughts?

// [1] -- in principle, at least (for completeness, there's an allowance for nonconstant functions w/ mutable iterators)

1 comments
I love const, but I am strongly opposed to adding constness here - as I mentioned, the non-Standard "for each" extension did that, and it caused endless confusion. Note that the lambda precedent is not actually applicable, because it affects only value captures. Reference captures can always be written through. The purpose of next-gen range-for is to operate in-place, i.e. with reference semantics.

for_each() does not add constness, and can modify elements in-place. The fact that it is grouped with the "non-modifying algorithms" is a confusing historical artifact (and was actually the subject of the first Library Issue I had a part in filing) - the algorithm itself does not modify things (unlike sort(), say) but the given functor can.

Thanks for the reply!

Interesting that the constness in `for each` caused confusion (did it offer a `mutable` opt-in, though?), would intuitively expect it to be the POLS behavior. I guess given that you were probably receiving feedback on that, I will take it as something to be acknowledged.

I can see the reference semantics point, in this context the difference from the lambdas seems to make more sense.

// Just to explore another avenue, again mostly out of curiosity :-), how realistic (from the impl. POV) would be to have value-semantic range-based `for` with _mandated_ copy elision whenever possible?

True about `std::for_each`, that's what I've referred to as the "allowance" for the mutable iterators, wasn't aware about the grouping being merely a historical artifact, though. I've always felt a bit dirty using it for mutation[1], it seems that maybe unnecessarily so :-)

// [1] -- perhaps due to the algorithm being specified in terms of the InputIterator concept; hm, that being said, I suppose that while it only guarantees that we can read (dereferenced) `it`, it doesn't say that `it` _itself_ has to be immutable (right?), so it could be that I should think of a better metaphor to internalize. How do you think about InputIterators?

> did it offer a `mutable` opt-in, though?

Nope.

> would intuitively expect it to be the POLS behavior.

If you give me (the implementation) a modifiable range, and I invisibly add constness before giving you back an element, that's surprising. C++ doesn't add constness by default anywhere (with the novel exception of lambda function call operators). If you give me a modifiable range, the least surprising thing to do is to give you a modifiable element, because that's what all of ptr[idx], * ptr, and * iter would do.

> how realistic (from the impl. POV) would be to have value-semantic range-based `for` with _mandated_ copy elision whenever possible?

If you say "for (auto elem : range) { elem = stuff; }" the write to the elem-copy will be dropped on the floor. Copy elision can't solve that.

> it doesn't say that `it` _itself_ has to be immutable (right?)

Correct.

> How do you think about InputIterators?

The concept is single-pass, read-only, but a given iterator may be stronger. for_each() is kind of special (it's the only algorithm that takes InputIterators, yet allows stronger iterators to be used with modifying functors), but other algorithms are vaguely similar. For example, transform(InIt first, InIt last, OutIt result, UnOp op) permits transform(first, last, first, op) for an in-place transformation - here, first/last's type clearly has to be InIt and OutIt simultaneously, i.e. a mutable FwdIt iterator or stronger.

Thanks for the explanations!