Date: Sat, 2 Aug 2025 13:21:00 +0000
One way to fix this issue I see is to impose that `returns_from_caller` can only be used with lambda, and the "caller scope" would be the function that defined the lambda. It's what makes the most sense when reading / writing code (you even assumed it in your example), and it avoids all the issues of the functor returning when it should not and breaking everything.
Now, there is still a problem with the normal return type to be used by that kind of function. Void? won't work with your example. Let the user write it explicitly? Feels really bad and the return type syntax of the lambda should really be used for the type of the caller-return. A solution could be to use a type that is implicitly convertible to every other type. That way, the monadic operation could handle it as if it was a normal function (at least syntax-wise). And then, the compiler can generate the code necessary to punch through all the way to the caller, as he knows when a functor is `returns_from_caller` (which means that the `returns_from_caller` must be part of the type).
Could these cause issues with the monadic function itself? For std::optional and std::expected, these shouldn't cause any issue as they don't impose anything on the ability of the callback to throw (at least not from what I saw in cppreference). And this feature kind of act like exceptions in a way, but in a more controlled and expressiveness in the returning. But for custom functions, it will depend on there capacity to be stop on invocation of there callback.
One other restriction I think should be used is the inability to use such a lambda in a destructor. Or maybe even entirely forbidding its use as a non-static data member. See the following example:
```cpp
template <typename Func>
struct RAIIWrapper {
RAIIWrapper(Func&& func) : func {std::forward<Func> (func)} {}
~RAIIWrapper() {func();}
Func func;
};
auto someFunc() -> std::expected<int, float> {
RAIIWrapper([]() returns_from_caller {return std::unexpected(69.f);});
return 42;
}
```
Here we have a clear conflict. When the destructor of RAIIWrapper is called, the function already has returned in a way. But that destructor wants to return too, and with a different result.
________________________________
De : Std-Proposals <std-proposals-bounces_at_[hidden]> de la part de Iúri Chaer via Std-Proposals <std-proposals_at_[hidden]>
Envoyé : samedi, 2 août 2025 12:08
À : Andre Kostur <andre_at_[hidden]>
Cc : Iúri Chaer <iuri.chaer_at_[hidden]>; std-proposals <std-proposals_at_[hidden]>
Objet : Re: [std-proposals] caller_return - a step in the direction of nicer error-by-value
Oh... I see now the complexity... return would have to punch through the monadic function as well, and it constrains callable acceptors wrt layering function calls. Which makes me want to set an anchor first, bringing back bad memories of setjmp(). Yeah, my mental model for how something like this could work definitely needs more time to mature.
On Sat, 2 Aug 2025, 10:41 Iúri Chaer, <iuri.chaer_at_[hidden]<mailto:iuri.chaer_at_[hidden]>> wrote:
> returns a std::expected, and the user wants for the
control flow to continue in the calling function if it does get the expected type, but return immediately if it gets the unexpected type.
That's essentially what the sample `returns_from_caller` code does, but I think it's more interesting than what I understood of P2561 because it's not necessarily aimed at errors, it would work with any monadic function. Maybe I have multiple alternative functions returning `std::optional` and I want to do some postprocessing and return as soon as one of them succeeds (think multiplatform code).
The advantage of it being a property of the callable is that you can compose with it. P2561's `try?` would boil down to something like:
auto propagate_unexpected(auto err) returns_from_caller {
return std::unexpected(err);
}
foo.or_else(&propagate_unexpected);
But you could do many other interesting things as well. I use `return` a lot in my code, it's a great way to avoid nested if/else blocks. I do it both in case of error and of success, and it's not exclusive to a single type. Like:
expected<symbols, std::string> symbolicate(const std::string& maybe_crashlog) {
auto addresses = stack_addresses::parse(maybe_crashlog).or_else(
[]() returns_from_caller {
return std::unexpected<std::string>("no addresses found");
});
std::string errors;
errors += addr2line::symbolicate(addresses).and_then(
[](symbols s) returns_from_caller {
return s;
}) + ", ";
errors += darwin_atos::symbolicate(addresses).and_then(
[](symbols s) returns_from_caller {
return s;
}) + ", ";
errors += win_cdb::symbolicate(addresses).and_then(
[](symbols s) returns_from_caller {
return s;
});
return std::unexpected(errors);
}
On Fri, 1 Aug 2025, 15:38 Andre Kostur, <andre_at_[hidden]<mailto:andre_at_[hidden]>> wrote:
The first example that Ayrton started with. There exists a called
function that returns a std::expected, and the user wants for the
control flow to continue in the calling function if it does get the
expected type, but return immediately if it gets the unexpected type.
This description even encapsulates my thoughts on this feature in
that note that I didn't have to say anything else about the called
function, only that it returned a std::expected. I think P2561
addresses the use-case better.
This proposal feels like exceptions, just using a different mechanism
(and is more syntactically invasive).
On Fri, Aug 1, 2025 at 6:52 AM Iúri Chaer via Std-Proposals
<std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]>> wrote:
>
> > Not all return paths in the callable should cause the caller to immediately return.
>
> What's the usage scenario you have in mind? The initial example from the thread looks like it suffers from the same type of ergonomic constraint that keeps me from using monadic functions more frequently, which doesn't require that sort of complexity... I have to agree with the others who compared that form of the proposal with exceptions, it's hard to reason about control flow in the presence of something like that.
>
> On Fri, 1 Aug 2025 at 14:07, Sebastian Wittmeier via Std-Proposals <std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]>> wrote:
>>
>> -----Ursprüngliche Nachricht-----
>> Von: Andre Kostur via Std-Proposals <std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]>>
>>
>>
>>
>> >Not all return paths in the callable should cause the caller to immediately return.
>>
>>
>> >I’m still thinking that the callable shouldn’t have to know about this, and it should be entirely within the caller’s domain as to whether to >return immediately or not.
>>
>>
>>
>>
>>
>> Then why not create a special command to invoke the callable.
>>
>> The caller can decide, whether to use this command or not..
>>
>>
>>
>>
>>
>> OTOH should this work the same as exceptions?
>>
>> Why not use the same or similar language constructs?
>>
>> Probably the in-between functions need to know more about, which exceptions can be thrown.
>>
>>
>>
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]<mailto:Std-Proposals_at_[hidden]>
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]<mailto:Std-Proposals_at_[hidden]>
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
Now, there is still a problem with the normal return type to be used by that kind of function. Void? won't work with your example. Let the user write it explicitly? Feels really bad and the return type syntax of the lambda should really be used for the type of the caller-return. A solution could be to use a type that is implicitly convertible to every other type. That way, the monadic operation could handle it as if it was a normal function (at least syntax-wise). And then, the compiler can generate the code necessary to punch through all the way to the caller, as he knows when a functor is `returns_from_caller` (which means that the `returns_from_caller` must be part of the type).
Could these cause issues with the monadic function itself? For std::optional and std::expected, these shouldn't cause any issue as they don't impose anything on the ability of the callback to throw (at least not from what I saw in cppreference). And this feature kind of act like exceptions in a way, but in a more controlled and expressiveness in the returning. But for custom functions, it will depend on there capacity to be stop on invocation of there callback.
One other restriction I think should be used is the inability to use such a lambda in a destructor. Or maybe even entirely forbidding its use as a non-static data member. See the following example:
```cpp
template <typename Func>
struct RAIIWrapper {
RAIIWrapper(Func&& func) : func {std::forward<Func> (func)} {}
~RAIIWrapper() {func();}
Func func;
};
auto someFunc() -> std::expected<int, float> {
RAIIWrapper([]() returns_from_caller {return std::unexpected(69.f);});
return 42;
}
```
Here we have a clear conflict. When the destructor of RAIIWrapper is called, the function already has returned in a way. But that destructor wants to return too, and with a different result.
________________________________
De : Std-Proposals <std-proposals-bounces_at_[hidden]> de la part de Iúri Chaer via Std-Proposals <std-proposals_at_[hidden]>
Envoyé : samedi, 2 août 2025 12:08
À : Andre Kostur <andre_at_[hidden]>
Cc : Iúri Chaer <iuri.chaer_at_[hidden]>; std-proposals <std-proposals_at_[hidden]>
Objet : Re: [std-proposals] caller_return - a step in the direction of nicer error-by-value
Oh... I see now the complexity... return would have to punch through the monadic function as well, and it constrains callable acceptors wrt layering function calls. Which makes me want to set an anchor first, bringing back bad memories of setjmp(). Yeah, my mental model for how something like this could work definitely needs more time to mature.
On Sat, 2 Aug 2025, 10:41 Iúri Chaer, <iuri.chaer_at_[hidden]<mailto:iuri.chaer_at_[hidden]>> wrote:
> returns a std::expected, and the user wants for the
control flow to continue in the calling function if it does get the expected type, but return immediately if it gets the unexpected type.
That's essentially what the sample `returns_from_caller` code does, but I think it's more interesting than what I understood of P2561 because it's not necessarily aimed at errors, it would work with any monadic function. Maybe I have multiple alternative functions returning `std::optional` and I want to do some postprocessing and return as soon as one of them succeeds (think multiplatform code).
The advantage of it being a property of the callable is that you can compose with it. P2561's `try?` would boil down to something like:
auto propagate_unexpected(auto err) returns_from_caller {
return std::unexpected(err);
}
foo.or_else(&propagate_unexpected);
But you could do many other interesting things as well. I use `return` a lot in my code, it's a great way to avoid nested if/else blocks. I do it both in case of error and of success, and it's not exclusive to a single type. Like:
expected<symbols, std::string> symbolicate(const std::string& maybe_crashlog) {
auto addresses = stack_addresses::parse(maybe_crashlog).or_else(
[]() returns_from_caller {
return std::unexpected<std::string>("no addresses found");
});
std::string errors;
errors += addr2line::symbolicate(addresses).and_then(
[](symbols s) returns_from_caller {
return s;
}) + ", ";
errors += darwin_atos::symbolicate(addresses).and_then(
[](symbols s) returns_from_caller {
return s;
}) + ", ";
errors += win_cdb::symbolicate(addresses).and_then(
[](symbols s) returns_from_caller {
return s;
});
return std::unexpected(errors);
}
On Fri, 1 Aug 2025, 15:38 Andre Kostur, <andre_at_[hidden]<mailto:andre_at_[hidden]>> wrote:
The first example that Ayrton started with. There exists a called
function that returns a std::expected, and the user wants for the
control flow to continue in the calling function if it does get the
expected type, but return immediately if it gets the unexpected type.
This description even encapsulates my thoughts on this feature in
that note that I didn't have to say anything else about the called
function, only that it returned a std::expected. I think P2561
addresses the use-case better.
This proposal feels like exceptions, just using a different mechanism
(and is more syntactically invasive).
On Fri, Aug 1, 2025 at 6:52 AM Iúri Chaer via Std-Proposals
<std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]>> wrote:
>
> > Not all return paths in the callable should cause the caller to immediately return.
>
> What's the usage scenario you have in mind? The initial example from the thread looks like it suffers from the same type of ergonomic constraint that keeps me from using monadic functions more frequently, which doesn't require that sort of complexity... I have to agree with the others who compared that form of the proposal with exceptions, it's hard to reason about control flow in the presence of something like that.
>
> On Fri, 1 Aug 2025 at 14:07, Sebastian Wittmeier via Std-Proposals <std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]>> wrote:
>>
>> -----Ursprüngliche Nachricht-----
>> Von: Andre Kostur via Std-Proposals <std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]>>
>>
>>
>>
>> >Not all return paths in the callable should cause the caller to immediately return.
>>
>>
>> >I’m still thinking that the callable shouldn’t have to know about this, and it should be entirely within the caller’s domain as to whether to >return immediately or not.
>>
>>
>>
>>
>>
>> Then why not create a special command to invoke the callable.
>>
>> The caller can decide, whether to use this command or not..
>>
>>
>>
>>
>>
>> OTOH should this work the same as exceptions?
>>
>> Why not use the same or similar language constructs?
>>
>> Probably the in-between functions need to know more about, which exceptions can be thrown.
>>
>>
>>
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]<mailto:Std-Proposals_at_[hidden]>
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]<mailto:Std-Proposals_at_[hidden]>
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
Received on 2025-08-02 13:21:07