On Sun, Sep 15, 2024 at 1:55 PM Andrzej Krzemienski
Hi Steve, Thanks for weighing in.
niedz., 15 wrz 2024 o 16:42 Steve Downey via Boost
napisał(a): The core motivation is not having yet another optional-like in the standard library.
Understood. However, there is a trade-off here: additional optional-like type versus additional member functions in std::optional.
Ranges needs a 0-or-1 range as a primitive for where the filter algorithm isn't quite the right answer, in particular for range comprehension desugaring which comes up fairly often by hand.
I am not well familiar with ranges, so I must admit that upon reading the phrase "range comprehension desugaring" I bailed out. Is there a place that describes the term? However, I understood that in order to effectively compose ranges one needs the additional component, which can either be implemented as a stand alone ranges component or through adding member functions to `optional`.
Comprehensions are the expressions in other languages like [ (x,y) | x <- [1, 2, 3], y <- [4, 5, 6], is_even(x+y)] based on set comprehension notation. Python has it as [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y] The boolean constraint becomes an expression that is like if(cond) return [val] else return [], either an empty list or a list of one element that is the values under examination. We don't have that in C++, but it's an important technique in writing range pipelines. That's how you turn [(x, y, z) | z <- [1...], x <- [1...z+1], y <- [x ... z+1], (x*x + y*y) == z*z] into something like auto triples = and_then(iota(1), [](int z) { return and_then(iota(1, z + 1), [=](int x) { return and_then(iota(x, z + 1), [=](int y) { return yield_if(x * x + y * y == z * z, std::make_tuple(x, y, z)); }); }); }); Filter can be expressed as joining over yield_if, but not all expressions using the idiom go the other way, at least not trivially.
Having a range type like empty or single, without a bunch of the implicit
conversion operations from the underlying type, was my preference, however adding the range ops to optional<T> was a reasonable fallback.
Indeed, I would also think that if this is only needed to compose ranges, it should be added as a <ranges>-component: view, adapter.
wg21.link/P1255R10 The criticism was "do we really need another optional?" The main difference being that it did not allow direct assignment from the underlying, because that's what introduces ambiguity, particularly for references. And optional not being a container was always a suspect position,
especially after the addition of other fixed size or limited size containers. Making it a full Cpp17 Container is probably over the top, and might have confused existing code, but adding the range interface is minimal.
In retrospect, I think I agree with Tony Van Eerd's observation that it was a design mistake to combine two models in `boost::optional`, and by extension in `std::optional`: 1. That optional<T> is a T with an additional state. hence the conversions and comparisons with `T` and `none`/`nullopt`. 2. That optional<T> is a container holding zero or one element of `T` hence the interface for querying for being empty and for accessing the element.
Those two are fine together. It's that it's also (sometimes) transparently a T that causes the conceptual muddy-ness. But that's important for modelling optional parameters, void func(int a = optional<int>{}); func(5);
And making optional model a fancy pointer just made additional issues. . But now that we have this confusion, I am not sure if extending one of
these interfaces further is the right way to go.
I can think of a couple of reasons why adding a range interface to optional is not desired. 1. A general design principle that a class should have a minimal interface required to manage its invariant. Anything else, if needed, should be free functions operating on the class interface. The Standard Library components do not necessarily follow it (std::string being the best (worst) example). 2. Given that we have standard concepts that detect the range interface, `std::ranges::range` it is reasonable to assume that programmers use it in their code, also for controlling the overload resolution. Suddenly adding the range interface to optional is likely to break their code. (While any change whatsoever could theoretically break code, the current problem is more likely, because we are talking about the standardized concept.) 3. Increasing (thus complicating) the class interface without motivating usages is a bad trade-off.
The usages for the interop with ranges were given, but it looks like there are no other usages not involving ranges. The usage with the for-loop indeed seems like a hack rather than a technique that I would comfortably endorse.
Regards, &rzej;
On the other hand, once we have std::optional
and possibly optional , boost's optional should probably be avoided in codebases using C++26 or later. On Sat, Sep 14, 2024 at 7:59 AM Thomas Fowlery via Boost < boost@lists.boost.org> wrote:
Every feature should be well motivated by a real use case or a set of use cases. I don't find any of the examples in this proposal motivating. In fact, this feature seems to encourage bad code, judging by the examples: - using transform(f) | join with an optional-returning function f, instead of using a simple filter(predicate) - materializing optionals when not really necessary (incurs extra moves/copies) - abusing the range-for loop for unwrapping optionals
Best regards, Thomas
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost