Date: Sun, 09 Jun 2024 23:08:04 +0200
Hi,
thanks for your answer.
On Sun, 2024-06-09 at 17:18 +0000, Tiago Freire wrote:
> I personally think that std::optional::transform is trying to do too much already as it is and there is no need for it
> to exist.
> It is clutter in the standard.
>
> The function of the optional is to either "get you a value or not get you the value" once you have the optional its
> job is done, you have it, you can do whatever you want with it. Trying to get it to do more than that is a clear
> violation of single responsibility.
> Instead of passing a lambda (or functor) to transform you could have passed the optional to a lambda (or functor)
> instead, the only downside would be that you would need to write "if(obj.has_value())" yourself (which isn't much and
> is trivial to write), that's it. On the flip side if you passed the optional to the functor instead of the functor to
> the optional you could implement an early exit (and you only need to check the condition once) in case you have
> multiple transform chains. I would be surprised if "std::optional::transform" had any performance advantages at all in
> any scenario.
>
> But, in any case, good or bad, it exists, what it does is not ultimately inconsistent, and now people want to use it.
> Ok!
>
> Let's go through your first proposal, what you are passing in is not a callable object, it's an offset into the object
> that might be contained in the optional, it cannot be called (if you tried it would fail).
> At the first glance it seems to be clear what you want to do. It's not a callable object, it's an offset into the
> object that could be hold by the optional, you can just put an "if constexpr" inside std::optional::transform to test
> which of the cases you are in and then do the right thing.
>
> Unless the object being pointed to is a function pointer, do you want to call the function? Or return the function
> pointer? There seems to be some ambiguity related to what exactly you would want to do here.
> You could say "hey why not have 2 interfaces instead? (like a std::optional::transform_callable and
> std::optional::transform_getter)", which would technically solve the ambiguity, but is there a better way? Maybe if
> the interface is presented differently, we could combine both of these and not have 2 different interfaces? Or perhaps
> avoid having 3 interfaces? After all, you are passing in an object, which essentially just some data, and calling a
> function or returning a value are merely categories of things that you can do with this data, and perhaps you might
> want to do other things besides returning a value or making a call.
>
I think it should behave somewhat similar to the projections of the ranges algorithms which is described by a
hypothetical INVOKE function. This means if you pass it a pointer-to-member-data it returns a reference to that member,
if you pass it a pointer-to-member-function it calls that member function on the object and returns the result.
So the ambiguity is resolved by defining the behavior.
As Giuseppe pointed out in his response, this cannot be transferred to optionals directly because in the case of a
pointer-to-member-data the result is a reference ans we do not have optional references.
> Let's remember what "std::optional::transform" does for us, which is check "if(obj.has_value())", and if it does then
> do something with the data.
> What if instead "std::optional::transform" after checking if it has a value, it just gives that object back to us and
> then let us decide what we want to do with it (call a function, return a data member, or whatever)? And we can do this
> by passing in a functor to "std::optional::transform" that receives the objects and allows us to access its content,
> and... oh wait... that is what "std::optional::transform" already does.
>
> The conclusion of this exercise is that "std::optional::transform" is already the ideal interface for what it is
> trying to do, and you would not be improving on this. You could argue that an extra 15 characters is too much to
> write... but... really...?
Yes, what I want can already be done. My proposal is to make it more concise and easier readable. And my proposal leans
on the precedence of having something very similar already in the standard in the form of the projections in the ranges
algorithms.
I can respect the view that transform (and I guess and_then and or_else) add "too much" to std::optional.
> I would hate to add anything else, as I have stated, "std::optional::transform" doesn't have a reason to exists to
> begin with, it is a violation of single responsibility, and it does nothing that couldn't have been done better
> without attaching this useless interface to std::optional. Once you have the optional, you have the optional, its job
> is done, you can do whatever you want with it after.
>
> An argument could be made for a "lazy invocation" version of "std::optional::value_or", that makes much more sense to
> me, even though you could also write a lambda around it to do the exact same thing and technically don't need it, at
> least I can see myself using that.
>
> Everything else, it is not wrong.... but it is lint, clutter... you don't need it, you won't miss it, it just bloats
> the standard for no reason.
>
> That’s my 2c
Thanks for providing that perspective.
>
> -----Original Message-----
> From: Std-Proposals <std-proposals-bounces_at_[hidden]> On Behalf Of Lorenz Quack via Std-Proposals
> Sent: Sunday, June 9, 2024 14:45
> To: std-proposals_at_[hidden]
> Cc: Lorenz Quack <code_at_[hidden]>
> Subject: [std-proposals] Proposal for std::optional extensions
>
> Hi std-proposals list,
>
> This is my first submission to this list.
>
> I have three proposal ideas that I would like to float here.
> They all concern std::optional.
> I have searched the archive of this mailing list and have not found similar proposals.
>
> In the below proposal ideas I will provide some examples that will assume the following code declarations:
>
> struct Foo {
> int bar;
> };
>
> std::optional<Foo> try_generate_foo();
>
> Foo safe_generate_foo()
>
>
> Without further ado, here are the proposal ideas:
>
> 1. Allow passing pointer-to-member (both data and function member) to std::optional::transform()
> Example:
> // before proposal
> int x = try_generate_foo().transform([](Foo& f){ return f.bar; });
>
> // after proposal
> int x = try_generate_foo().transform(&Foo::bar);
>
> This is similar to how the projections of the ranges algorithms allow pointer-to-members to be passed.
> If this proposal idea goes further it should be considered whether the exact same INVOKE semantics [1] should
> apply here.
>
> 2. Add the ability to call a function to create a default value.
> This could be in the form of a dedicated function like std::optional::value_or_call(Callable&& c) or an
> overload to optional::value_or
> Example:
> // before proposal
> auto optional_foo = try_generate_foo();
> if (optional_foo.has_value()) {
> return optional_foo.value();
> } else {
> return safe_generate_foo();
> }
>
> // after proposal
> return try_generate_foo().value_or_call(&safe_generate_foo);
>
> The name `value_or_call` is obviously a working title and would then need further discussion should this proposal
> idea gain traction.
>
> 3. add or_generate(Callable&& c)
> This should return `this` if the optional has a value and otherwise a new optional containing the result of calling
> the callable. This is different from or_else where the callable has to return an optional.
> This is analog to transform and and_then.
> Example:
> // before proposal
> try_generate_foo()
> .or_else([](){ return make_optional(safe_generate_foo()); })
> .and_then(...)
>
> // after proposal
> try_generate_foo()
> .or_generate(&safe_generate_foo)
> .and_then(...)
>
> Again, the name `or_generate` is just a working title.
>
>
>
> Looking forward to getting some feedback on these ideas.
>
> Best regards,
> Lorenz
>
>
> [1] https://en.cppreference.com/w/cpp/utility/functional#Function_invocation
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
thanks for your answer.
On Sun, 2024-06-09 at 17:18 +0000, Tiago Freire wrote:
> I personally think that std::optional::transform is trying to do too much already as it is and there is no need for it
> to exist.
> It is clutter in the standard.
>
> The function of the optional is to either "get you a value or not get you the value" once you have the optional its
> job is done, you have it, you can do whatever you want with it. Trying to get it to do more than that is a clear
> violation of single responsibility.
> Instead of passing a lambda (or functor) to transform you could have passed the optional to a lambda (or functor)
> instead, the only downside would be that you would need to write "if(obj.has_value())" yourself (which isn't much and
> is trivial to write), that's it. On the flip side if you passed the optional to the functor instead of the functor to
> the optional you could implement an early exit (and you only need to check the condition once) in case you have
> multiple transform chains. I would be surprised if "std::optional::transform" had any performance advantages at all in
> any scenario.
>
> But, in any case, good or bad, it exists, what it does is not ultimately inconsistent, and now people want to use it.
> Ok!
>
> Let's go through your first proposal, what you are passing in is not a callable object, it's an offset into the object
> that might be contained in the optional, it cannot be called (if you tried it would fail).
> At the first glance it seems to be clear what you want to do. It's not a callable object, it's an offset into the
> object that could be hold by the optional, you can just put an "if constexpr" inside std::optional::transform to test
> which of the cases you are in and then do the right thing.
>
> Unless the object being pointed to is a function pointer, do you want to call the function? Or return the function
> pointer? There seems to be some ambiguity related to what exactly you would want to do here.
> You could say "hey why not have 2 interfaces instead? (like a std::optional::transform_callable and
> std::optional::transform_getter)", which would technically solve the ambiguity, but is there a better way? Maybe if
> the interface is presented differently, we could combine both of these and not have 2 different interfaces? Or perhaps
> avoid having 3 interfaces? After all, you are passing in an object, which essentially just some data, and calling a
> function or returning a value are merely categories of things that you can do with this data, and perhaps you might
> want to do other things besides returning a value or making a call.
>
I think it should behave somewhat similar to the projections of the ranges algorithms which is described by a
hypothetical INVOKE function. This means if you pass it a pointer-to-member-data it returns a reference to that member,
if you pass it a pointer-to-member-function it calls that member function on the object and returns the result.
So the ambiguity is resolved by defining the behavior.
As Giuseppe pointed out in his response, this cannot be transferred to optionals directly because in the case of a
pointer-to-member-data the result is a reference ans we do not have optional references.
> Let's remember what "std::optional::transform" does for us, which is check "if(obj.has_value())", and if it does then
> do something with the data.
> What if instead "std::optional::transform" after checking if it has a value, it just gives that object back to us and
> then let us decide what we want to do with it (call a function, return a data member, or whatever)? And we can do this
> by passing in a functor to "std::optional::transform" that receives the objects and allows us to access its content,
> and... oh wait... that is what "std::optional::transform" already does.
>
> The conclusion of this exercise is that "std::optional::transform" is already the ideal interface for what it is
> trying to do, and you would not be improving on this. You could argue that an extra 15 characters is too much to
> write... but... really...?
Yes, what I want can already be done. My proposal is to make it more concise and easier readable. And my proposal leans
on the precedence of having something very similar already in the standard in the form of the projections in the ranges
algorithms.
I can respect the view that transform (and I guess and_then and or_else) add "too much" to std::optional.
> I would hate to add anything else, as I have stated, "std::optional::transform" doesn't have a reason to exists to
> begin with, it is a violation of single responsibility, and it does nothing that couldn't have been done better
> without attaching this useless interface to std::optional. Once you have the optional, you have the optional, its job
> is done, you can do whatever you want with it after.
>
> An argument could be made for a "lazy invocation" version of "std::optional::value_or", that makes much more sense to
> me, even though you could also write a lambda around it to do the exact same thing and technically don't need it, at
> least I can see myself using that.
>
> Everything else, it is not wrong.... but it is lint, clutter... you don't need it, you won't miss it, it just bloats
> the standard for no reason.
>
> That’s my 2c
Thanks for providing that perspective.
>
> -----Original Message-----
> From: Std-Proposals <std-proposals-bounces_at_[hidden]> On Behalf Of Lorenz Quack via Std-Proposals
> Sent: Sunday, June 9, 2024 14:45
> To: std-proposals_at_[hidden]
> Cc: Lorenz Quack <code_at_[hidden]>
> Subject: [std-proposals] Proposal for std::optional extensions
>
> Hi std-proposals list,
>
> This is my first submission to this list.
>
> I have three proposal ideas that I would like to float here.
> They all concern std::optional.
> I have searched the archive of this mailing list and have not found similar proposals.
>
> In the below proposal ideas I will provide some examples that will assume the following code declarations:
>
> struct Foo {
> int bar;
> };
>
> std::optional<Foo> try_generate_foo();
>
> Foo safe_generate_foo()
>
>
> Without further ado, here are the proposal ideas:
>
> 1. Allow passing pointer-to-member (both data and function member) to std::optional::transform()
> Example:
> // before proposal
> int x = try_generate_foo().transform([](Foo& f){ return f.bar; });
>
> // after proposal
> int x = try_generate_foo().transform(&Foo::bar);
>
> This is similar to how the projections of the ranges algorithms allow pointer-to-members to be passed.
> If this proposal idea goes further it should be considered whether the exact same INVOKE semantics [1] should
> apply here.
>
> 2. Add the ability to call a function to create a default value.
> This could be in the form of a dedicated function like std::optional::value_or_call(Callable&& c) or an
> overload to optional::value_or
> Example:
> // before proposal
> auto optional_foo = try_generate_foo();
> if (optional_foo.has_value()) {
> return optional_foo.value();
> } else {
> return safe_generate_foo();
> }
>
> // after proposal
> return try_generate_foo().value_or_call(&safe_generate_foo);
>
> The name `value_or_call` is obviously a working title and would then need further discussion should this proposal
> idea gain traction.
>
> 3. add or_generate(Callable&& c)
> This should return `this` if the optional has a value and otherwise a new optional containing the result of calling
> the callable. This is different from or_else where the callable has to return an optional.
> This is analog to transform and and_then.
> Example:
> // before proposal
> try_generate_foo()
> .or_else([](){ return make_optional(safe_generate_foo()); })
> .and_then(...)
>
> // after proposal
> try_generate_foo()
> .or_generate(&safe_generate_foo)
> .and_then(...)
>
> Again, the name `or_generate` is just a working title.
>
>
>
> Looking forward to getting some feedback on these ideas.
>
> Best regards,
> Lorenz
>
>
> [1] https://en.cppreference.com/w/cpp/utility/functional#Function_invocation
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
Received on 2024-06-09 21:08:09