int f() { auto&& l = [&return](int i){ return i; }; (l(5), puts("hello")); }
int f() {
auto&& l = [&return](int i){
return i;
};
(l(5), puts("hello"));
}
But it does unwind the stack like throwing an exception, right? So it can unwind multiple stack frames (as long as they're within the original capture point) and unwind temporaries within expressions around stack nesting points for function calls.
Do you have an example showing how it could be useful with the algorithms library e.g. for early return from accumulate?
ErrorCode save()
{
// note that default exit is not the same as return here
auto return_if = [&return](auto retval){
if (retval) { return retval; }
};
return_if(openFile());
return_if(saveData());
return_if(closeFile());
}
std::variant<Record, ErrorCode> load()
{
auto pass_error = [error = &return](auto ret){
// error looks like a [[noreturn]] function here
if (ret.index())
error(std::get<ErrorCode>(ret));
return std::get<0>(ret);
};
auto head = pass_error(loadHead());
auto tail = pass_error(loadTail());
return head + tail;
}
for (auto&& elem : myVector)
{
auto onBreak = [&break](){ std::cout << "Match: " << elem; break; }
if( isPrime(elem) )
onBreak();
// ....
if( isSquare(elem) )
onBreak();
// not sure if we want to allow this:
//[&, break = &onBreak]{
// if( isFibonacci(elem) )
// break;
//}();
}
for (auto&& person : people)
{
auto break_twice = [&break]{ break; }
for (auto&& phone : person.phones)
{
if (isLandline(phone))
{
std::cout << person.name << " " << phone
<< " still uses landline, do not switch it off!"
<< std::endl;
break_twice();
}
}
}
template<typename Range, typename Body, typename Else>
void for_or(const Range& r, const Body& b, Else e)
{
auto&& it = r.begin();
auto&& itEnd = r.end();
if (it != itEnd)
{
do
{
b(*it);
} while (++it != itEnd);
}
else
{
e();
}
}
template<typename CPlugin>
bool initPlugins(const CPlugin& plugins)
{
for_or(plugins, [=return, &break, &continue, &](const Plugin& plugin) {
// this means we have initPlugin's return and calling point's break, continue
if (!plugin.requires_init())
continue;
if (!plugin.init())
return false;
}, [&return] {
return DefaultPlugin::initialize();
}
return true;
}
Hi,
Let's put aside calling them monads for a while, we'll return to that later. First some examples:
1. Error codes when you have no exceptions (due to embedded system / manager's wisdom / using vast amount of C libraries that return error codes). Here we assume that bool(ErrorCode) == true iff there was an error.
ErrorCode save()
{
// note that default exit is not the same as return here
auto return_if = [&return](auto retval){
if (retval) { return retval; }
};
return_if(openFile());
return_if(saveData());
return_if(closeFile());
}
ErrorCode save()
{
std::function<void(ErrorCode)> return_if = [&return](auto retval){
if (retval) { return retval; }
};
return_if(openFile());
return_if(saveData());
return_if(closeFile());
}
-----
And now, the 'monadic benefits' :). You have seen that this essentially superior to for-else and for-break and break(n), albeit with a clumsy syntax.
Which you can make a macro for.
On 10 Sep 2016 04:46, "Nicol Bolas" <jmck...@gmail.com> wrote:
> See, it sounds like you're trying to say that returning from within the lambda will cause it to return from... well, that right there is the question. I think you want it to return from the function that called it. But how exactly is that supposed to happen?
Stack unwinding.
> After all, the function that called it is not necessarily the function that created it. What happens if you do this:
>
> ErrorCode save()
> {
> std::function<void(ErrorCode)> return_if = [&return](auto retval){
>
> if (retval) { return retval; }
> };
>
> return_if(openFile());
> return_if(saveData());
> return_if(closeFile());
> }
>
> I have every reason to expect this to achieve the same effect. Yet I cannot imagine how that would be possible. After all, I don't call it. I call `std::function::operator()`, which (eventually) calls the lambda. So the lambda will just provoke the return of some construct within the `std::function::operator()`.
Stack unwinding isn't just for exceptions. The machinery is there for non local jumps, such as thread cancellation, setjmp and SEH.
> And you can't say that it will always return to the caller of the lambda's creator, because I don't have to still be on the stack. I can return that lambda to someone else, wrapped in a `std::function`. How does that work?
UB, just as with capturing local variables by reference. Likewise if any of the intervening frames are not unwinding aware.
> A lambda in C++ is not some magical construct, imbued with fantastical powers. In C++, a lambda is an object. Not only that, it is an instance of a class type. You can rewrite any lambda as a local struct with a constructor and an operator() overload (except for generic lambdas, but I'd like to see that fixed). As it currently stands, a lambda cannot do anything that a user-created class can't also do.
There's another feature you're forgetting. Lambda captures can copy arrays by value, which user code can't do without some heavy library machinery.
If we're talking user code equivalents, setjmp would do if it's unwind-enabled, as would a custom exception in the absence of catch (...) blocks, or even invoking the platform unwind machinery directly.
> <snip>
Sure, I get that you like that lambdas are mostly syntactic sugar. That was probably necessary at the beginning, but it doesn't mean they have to stay that way.
On 10 Sep 2016 04:46, "Nicol Bolas" <jmck...@gmail.com> wrote:
> See, it sounds like you're trying to say that returning from within the lambda will cause it to return from... well, that right there is the question. I think you want it to return from the function that called it. But how exactly is that supposed to happen?Stack unwinding.
> After all, the function that called it is not necessarily the function that created it. What happens if you do this:
>
> ErrorCode save()
> {
> std::function<void(ErrorCode)> return_if = [&return](auto retval){
>
> if (retval) { return retval; }
> };
>
> return_if(openFile());
> return_if(saveData());
> return_if(closeFile());
> }
>
> I have every reason to expect this to achieve the same effect. Yet I cannot imagine how that would be possible. After all, I don't call it. I call `std::function::operator()`, which (eventually) calls the lambda. So the lambda will just provoke the return of some construct within the `std::function::operator()`.Stack unwinding isn't just for exceptions. The machinery is there for non local jumps, such as thread cancellation, setjmp and SEH.
auto x = []() {... throw return_value(someValue);} //Class template deduction
try
{
auto y = x(foo);
}
catch(return_value<ReturnType> &t)
{
return std::move(t.get());
}
auto y = try x(foo);
> And you can't say that it will always return to the caller of the lambda's creator, because I don't have to still be on the stack. I can return that lambda to someone else, wrapped in a `std::function`. How does that work?
UB, just as with capturing local variables by reference. Likewise if any of the intervening frames are not unwinding aware.
> A lambda in C++ is not some magical construct, imbued with fantastical powers. In C++, a lambda is an object. Not only that, it is an instance of a class type. You can rewrite any lambda as a local struct with a constructor and an operator() overload (except for generic lambdas, but I'd like to see that fixed). As it currently stands, a lambda cannot do anything that a user-created class can't also do.
There's another feature you're forgetting. Lambda captures can copy arrays by value, which user code can't do without some heavy library machinery
If we're talking user code equivalents, setjmp would do if it's unwind-enabled, as would a custom exception in the absence of catch (...) blocks, or even invoking the platform unwind machinery directly.
> <snip>
Sure, I get that you like that lambdas are mostly syntactic sugar. That was probably necessary at the beginning, but it doesn't mean they have to stay that way.
Been following this thread, and I just want to chip in with a very important aspect: Readability. In ten cases of ten, I'd use exceptions as a mechanism to achieve the same behavior. Why? Because it's more readable, and not obfuscated.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/dcb70287-4ee4-4cae-9e09-4f7cab8d0db8%40isocpp.org.
This implies that [&return] is invalid for std::function<void(ErrorCode)>, while [=return] should do what you expect it to do. It also implies, if we use careful wording, that [&return] is available for functions and member functions - either by this capture, or the more convenient parameter syntax (e.g.,`int f(int a, T ret = &return) { ... }`).
However, I'd go one step further. We can now think of code blocks, lambdas and bodies of for-loops in a unified manner. I'm not saying to rewrite compilers to this style, but it simplifies understanding for me. E.g. a body {} of a for-loop (that's repeated by `for`)is nothing different from a lambda that captures &return, redefines break and continue; and a `for` becomes a transformation on the lambda. These transformations, if we wish, can be made available to the user at no additional cost.
Note that the complex checks that exception handling is built upon is unnecessary for this return logic. An exception must check each catch()-block in reverse of the calling order and check if there's a type match. For these captures, there's one type accepted and one point to return to (per captured item), thus no checks for each stack frame is necessary. It's also way more strict, providing compile-time errors if you pass an incorrect type (rather than runtime issues that you get with uncaught exceptions). The second is why I'd use this rather than exceptions.
As for the example with accumulate, `[return_accumulated=return](double a, double b){ if(b==0) return_accumulated(0); return a*b; }` is a trivial optimization.
Macros and syntax: I'm not committed to any specific syntax yet. The one I've listed above is indeed verbose, it's simply to help discuss the scope and functionality. At a later step we might refine syntax, simplify base cases, clean it up, etc. Right now what I'd like to see is if we could cover the usual for-extensions, non-local return/break/continue and similar, frequently asked feature requests - and if yes, could we unify the syntax a bit..
Thanks again for your help on this,
-lorro
Rather than bikeshedding the syntax, I think you need to take a step back and explain what this proposed feature is. Right now, it reads like one of those joke programming languages like INTERCAL or TURKEY BOMB where words are just thrown together for comedic effect, rather than for intrinsic meaning (let alone usefulness).What would it mean for a function to "break, but in its caller's context"?You need to show a specific example of the semantics you're trying to achieve, in the form of- a library solution (perhaps involving setjmp/longjmp or exception-handling under the hood), and/or- assembly code for a non-trivial use of the proposed feature.
From: Arthur O'Dwyer Sent: Monday, September 12, 2016 8:58 PM To: ISO C++ Standard - Future Proposals Reply To: std-pr...@isocpp.org Subject: Re: [std-proposals] Re: capturing monads in lambda |
Been following this thread, and I just want to chip in with a very important aspect: Readability. In ten cases of ten, I'd use exceptions as a mechanism to achieve the same behavior. Why? Because it's more readable, and not obfuscated.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAEvHzA29Fv7c9Vk3bN5GwHjSbgNSGHEtUuNdhxAB3kENUWKt9g%40mail.gmail.com.
#define TAKE_RETURN /* syntax to be defined */
template<typename T>
int mul_for_accumulate(int a, int b, T return2 = TAKE_RETURN)
{
if (a==0 || b==0) return2(0);
return a * b;
}
while(true)
{
auto my_backtrack = TAKE_CONTINUE;
f(..., my_backtrack);
break;
}
for ( int i : range ) { ... }
for ([&, return = TAKE_RETURN]
(int i, auto continue = TAKE_CONTINUE, auto break = TAKE_BREAK) { ... }
)(range)
This could be combined with something I've been thinking about: a macro facility.
Motivating example:
template <typename... Args>
void log(const char* fmt, Args&&... args) {
if (logging_enabled) {
do_log(fmt, std::forward<Args>(args)...);
}
}
Now, the problem with this is that if an arg is expensive to compute, we take the cost before the if (logging_enabled) check.
What we could have instead is define log as a macro. Each arg
would then not be captured as a reference, but as a lambda that
yields the argument:
template <typename... Args>
macro void log(const char* fmt, Args&&... args) {
if (logging_enabled) {
do_log(fmt, args()...);
}
}
An argument x in log("%s", x) would be translated as [&] () { return x; } -> decltype(x). Combined with capturing return/break/continue etc. we could introduce new syntax:
with_lock(my_lock) {
do things that need my_lock held
if (some_condition) return; // releases my_lock and
returns from enclosing scope
do more things
}
of course, need some syntax to capture the compound statement as
a lambda; Swift has support for that we could borrow.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/20bd8e3d-a795-4bf8-84e3-1f74ced450ed%40isocpp.org.
template<typename T>
T my_ternary(const bool cond, ->T true_t, ->T false_t)
{
if (cond)
return true_t();
return false_t();
}
int main()
{
int i = 0;
auto a = [&]() mutable { return ++i; };
auto b = [&]() mutable { return ++i; };
std::cout << my_ternary(rand()%2, a(), b()); // should print 1, no UB/IDV
}
#include <vector>
#include <iostream>
#include <experimental/optional>
#include <cmath>
#include <limits>
enum class return_mode { ret, brk, cont, back };
enum { parent_break };
enum { parent_continue };
enum { to_parent };
// TODO: specialize for void
template<typename R, typename P>
struct extended_return
{
const return_mode which_;
const std::experimental::optional<P> parent_retval_;
const std::experimental::optional<R> retval_;
extended_return(R retval)
: which_ (return_mode::back)
, retval_(std::move(retval)) {}
extended_return(decltype(to_parent), P retval)
: which_ (return_mode::ret)
, parent_retval_(std::move(retval)) {}
extended_return(decltype(parent_break) tgt)
: which_ (return_mode::brk) {}
extended_return(decltype(parent_continue) tgt)
: which_ (return_mode::cont) {}
operator return_mode() const { return which_; }
};
extended_return<double, double> mul2(double lhs, double rhs)
{
if (rhs == 0) {
return { to_parent, 0.0 };
}
return lhs * rhs;
}
double mul2(double lhs, double rhs, void(*parent_return)(double))
{
if (rhs == 0) {
parent_return(0.0);
}
return lhs * rhs;
}
#define take_return \
else if(ret == return_mode::ret) \
return *ret.parent_retval_;
#define take_break \
else if(ret == return_mode::brk) \
break;
#define take_continue \
else if(ret == return_mode::cont) \
continue;
#define invoke_with(TAKEN, F, ...) \
({ \
auto ret = F(__VA_ARGS__); \
if (false); \
TAKEN \
*ret.retval_; \
});
#define invoke_with_abs(R, F, ...) \
({ \
struct parent_return_t \
{ \
R value; \
}; \
auto parent_return = +[](R v) { \
throw parent_return_t{v}; \
}; \
std::experimental::optional< \
decltype(F(__VA_ARGS__, \
parent_return)) \
> ret; \
try { \
ret = F(__VA_ARGS__, \
parent_return); \
} catch (parent_return_t r) { \
return std::move(r.value); \
} catch (...) { \
throw; \
} \
*ret; \
});
template<typename R>
double prod(const R& r)
{
if (r.empty()) {
return NAN;
}
double p = 1.0;
for (auto ri : r) {
p = invoke_with(take_return, mul2, p, ri);
//p = invoke_with_abs(double, mul2, p, ri);
}
return p;
}
int main()
{
std::vector<int> v{1, 2, 0, 4};
std::cout << prod(v);
}
template<typename T>
void g(int i, T brk)
{
std:cout << i;
if (i % 2) brk();
}
bool f(std::vector<int> v)
{
for (auto&& i : v) {
g(i, []`break;`);
}
}
From: szollos...@gmail.com Sent: Saturday, October 15, 2016 1:25 PM |
To: ISO C++ Standard - Future Proposals Reply To: std-pr...@isocpp.org Subject: Re: [std-proposals] Re: capturing monads in lambda |
#include <vector>
#include <iostream>
extern bool g();
struct Break {};
int sum(const std::vector<int>& v)
{
int ret = 0;
for (auto&& elem : v) {
if (g(elem)) throw inline Break();
ret += elem;
}
}
int main()
{
std::vector<int> v{ 1, 2, 3 };
try {
std::cout << sum(v);
} catch inline (const Break&) {
std::cout << "break";
}
}
try { /* ... */
}
template<typename T>
catch (const T& t) { /* ...*/
}
enum MyEnum { a, b, c };
void visit(const MyEnum e) {
switch (e) {
template<MyEnum enumVal>
catch enumVal:
throw inline std::integral_constant<MyEnum, enumVal>();
}
}