Date: Thu, 31 Jul 2025 14:35:13 +0000
Hello,
This proposal is about adding a way for a callee to return from the caller. Such a feature can be useful to avoid the multiplication of `if`(-`else`) blocks related to error handling, and more specifically the one that just forwards the error. For example, the following code :
```cpp
std::expected<std::string, int> someFailableOperation() noexcept;
std::expected<void, int> foo() noexcept {
auto result {someFailableOperation()};
if (!result)
return result.error();
std::println("The result: {}", *result);
return {};
}
```
would become the much nicer :
```cpp
std::expected<void, int> foo() noexcept {
std::println("The result: {}", unwrap(result));
return {};
}
```
This `unwrap` function could even in the future be replaced by some operator (question mark ? certainly, but not sure if this should be in the same proposal).
First, we need to define some goals of this feature to make it play nice with existing code and for it to not create disturbing workflow :
1.
A function capable of caller-return must be tag as such in the prototype (even make it part of the type, like for noexcept?). In this way, not every random function can start doing it, and the abi-compatibility is preserved (as such a change would certainly require abi change).
2.
Having a way to get the caller-return-type in the callee, so we can make conditional error handling (mostly conversion).
3.
Having a way to force your function to be used only when the caller-return-type match certain requirements.
With those requirements, I propose the following syntax :
```cpp
(1) caller_return < _type-parameter_ > _function-declaration_
(2) caller_return < _type-parameter_ > requires _constraint_ _function-declaration_
```
Where:
*
type-parameter : a single type parameter, may be constrained
*
function-declaration : a usual function declaration, may be templated
*
constraint : some constraint on the type-parameter. The requires-clause is merged with the one from usual template, if the function is templated.
A function mark _caller-return_ is implicitly _inline_.
And we add the following usage for the `caller_return` keyword, inside a function scope :
```cpp
(1) caller_return;
(2) caller_return _expr_;
```
When this instruction is encountered, the caller is returned from, with the value of expr (or void for (1)). The type of the caller-return-value must meet the same requirements as if it was used in a normal return statement in the caller.
Of course, this usage of the keyword is limited to function marked caller-return.
With this syntax, the `unwrap` function of the example at the top can be written as follow :
```cpp
caller_return</* is-expected-concept */ CallerReturn>
template <typename Res, std::convertible_to<typename CallerReturn::error_type> Err>
constexpr Res unwrap(std::expected<Res, Err> exp) noexcept {
if (exp)
return *exp;
caller_return std::unexpected(static_cast<typename CallerReturn::error_type> (exp.error()));
}
```
There is still the question of which functions are allowed to be caller-return. Destructor should definitely not be caller-return. What about constructor? I don't have an opinion on whether they should be allowed or not. Operator overloading? One of the main appeals of this feature is the possibility to allow implementation of a question mark operator, so operator overloading should be allowed. But using it with the current operator can make the flow of a function not obvious at all (because who read the prototype of operators). And people will certainly use it on the dereference operator, which is not a behavior most C++ developer expect. So, I think it should be disallowed on operator overloading, except for a question mark operator (if one is added).
This proposal is about adding a way for a callee to return from the caller. Such a feature can be useful to avoid the multiplication of `if`(-`else`) blocks related to error handling, and more specifically the one that just forwards the error. For example, the following code :
```cpp
std::expected<std::string, int> someFailableOperation() noexcept;
std::expected<void, int> foo() noexcept {
auto result {someFailableOperation()};
if (!result)
return result.error();
std::println("The result: {}", *result);
return {};
}
```
would become the much nicer :
```cpp
std::expected<void, int> foo() noexcept {
std::println("The result: {}", unwrap(result));
return {};
}
```
This `unwrap` function could even in the future be replaced by some operator (question mark ? certainly, but not sure if this should be in the same proposal).
First, we need to define some goals of this feature to make it play nice with existing code and for it to not create disturbing workflow :
1.
A function capable of caller-return must be tag as such in the prototype (even make it part of the type, like for noexcept?). In this way, not every random function can start doing it, and the abi-compatibility is preserved (as such a change would certainly require abi change).
2.
Having a way to get the caller-return-type in the callee, so we can make conditional error handling (mostly conversion).
3.
Having a way to force your function to be used only when the caller-return-type match certain requirements.
With those requirements, I propose the following syntax :
```cpp
(1) caller_return < _type-parameter_ > _function-declaration_
(2) caller_return < _type-parameter_ > requires _constraint_ _function-declaration_
```
Where:
*
type-parameter : a single type parameter, may be constrained
*
function-declaration : a usual function declaration, may be templated
*
constraint : some constraint on the type-parameter. The requires-clause is merged with the one from usual template, if the function is templated.
A function mark _caller-return_ is implicitly _inline_.
And we add the following usage for the `caller_return` keyword, inside a function scope :
```cpp
(1) caller_return;
(2) caller_return _expr_;
```
When this instruction is encountered, the caller is returned from, with the value of expr (or void for (1)). The type of the caller-return-value must meet the same requirements as if it was used in a normal return statement in the caller.
Of course, this usage of the keyword is limited to function marked caller-return.
With this syntax, the `unwrap` function of the example at the top can be written as follow :
```cpp
caller_return</* is-expected-concept */ CallerReturn>
template <typename Res, std::convertible_to<typename CallerReturn::error_type> Err>
constexpr Res unwrap(std::expected<Res, Err> exp) noexcept {
if (exp)
return *exp;
caller_return std::unexpected(static_cast<typename CallerReturn::error_type> (exp.error()));
}
```
There is still the question of which functions are allowed to be caller-return. Destructor should definitely not be caller-return. What about constructor? I don't have an opinion on whether they should be allowed or not. Operator overloading? One of the main appeals of this feature is the possibility to allow implementation of a question mark operator, so operator overloading should be allowed. But using it with the current operator can make the flow of a function not obvious at all (because who read the prototype of operators). And people will certainly use it on the dereference operator, which is not a behavior most C++ developer expect. So, I think it should be disallowed on operator overloading, except for a question mark operator (if one is added).
Received on 2025-07-31 14:35:17