C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Proposal for std::optional extensions

From: Tiago Freire <tmiguelf_at_[hidden]>
Date: Sun, 9 Jun 2024 17:18:46 +0000
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.

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

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


-----Original Message-----
From: Std-Proposals <std-proposals-bounces_at_lists.isocpp.org> 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_lorenzquack.de>
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_lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2024-06-09 17:18:53