cppfront
cppfront copied to clipboard
[SUGGESTION] Add possibility of using lambda in is-statements (inspect and if statements)
In https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2392r0.pdf there is an example that uses lambda with is
keyword.
cout << inspect i -> std::string {
is in(1,2) = "1 or 2";
is in(2,3) = "3";
is _ = "something else";
};
or
if i is in(10,20) {
cout << "I between 10 and 20" << endl;
}
I started to experiment with current implementation to achieve the same functionality and I came up with implementation that you can check here: https://godbolt.org/z/464PP7bW6 (working prototype).
Unfortunately lambda with the capture list cannot be argument of the template (only gcc supports that). So, I have created a wrapper that stores a lambda and the arguments that normally will go to the capture list.
#include <utility>
#include <tuple>
#include <iostream>
#include <vector>
constexpr auto less_than = [](auto value) {
return cpp2::lambda_wrapper([](auto x, auto capture) { return x < capture;}, value);
};
constexpr auto in = [](auto min, auto max) {
return cpp2::lambda_wrapper([](auto x, auto capture) {
return std::get<0>(capture) <= x && x <= std::get<1>(capture);
}, std::pair(min,max));
};
constexpr auto empty = cpp2::lambda_wrapper([](auto x){
return std::empty(x);
});
main: () -> int = {
i := 15;
if i is less_than(20) {
std::cout << "less than 20" << std::endl;
}
if i is in(10,30) {
std::cout << "i is between 10 and 30" << std::endl;
}
v : std::vector<int> = ();
if v is empty {
std::cout << "v is empty" << std::endl;
}
}
lambda_wrapper
shall use std::tuple
to store arguments that shall go to capture list but std::tuple
is not structural type and cannot be used in that context. There are possible workarounds (https://stackoverflow.com/questions/69194075/structural-tuple-like-wrapping-a-variadic-set-of-template-parameters-values-in) but I decided to propose something simple.
The current implementation can handle lambda that captures:
- none argument - using
std::monostate
that is default second template argument type (lambda_wrapper([](auto x){/**/})
), - one argument - (
lambda_wrapper([](auto x, auto capture){/**/}, captured_value)
), - two arguments - using
std::pair
(lambda_wrapper([](auto x, auto capture){/**/}, std::pair(/* captured two values */) )
),
If more captured values will be needed we need to add tuple-like structure that will be structural type.
On the last push I have made possible using lambda_wrapper
in inspect alternatives (by making id-expression ( expression-list )
an valid alternative syntax.
That makes this code works:
std::cout << inspect i -> std::string {
is less_than(10) = "i less than 10";
is in(11,20) = "i is between 11 and 20";
is _ = "i is out of our interest";
} << std::endl;
Ah... the change works in if statements but it is not working yet with inspect
- the patch to cppfront is needed not to perceive the syntax as an error.
On the last push I have added support for using id-expression ( expression-list )
as alternative in the inspect
expression.
I have noticed that my other PRs regarding using is()
with multiple types/templates interfere with this PR and I need to update it.
Currently, I am not able to allow generic lambda use as I don't know how to write requires clause for lambda_wrapper that will match them. Now it matches types that matches:
template <typename F, typename Capture = std::monostate>
requires (
requires { &F::operator(); }
)
struct lambda_wrapper;
What is currently possible is visible here:
#include <utility>
#include <tuple>
#include <iostream>
#include <vector>
#include <span>
constexpr auto less_then = [](int value) {
return cpp2::lambda_wrapper([](int x, int capture) { return x < capture;}, value);
};
constexpr auto in = [](auto min, auto max) {
return cpp2::lambda_wrapper([](int x, std::pair<int,int> capture) {
return std::get<0>(capture) <= x && x <= std::get<1>(capture);
}, std::pair(min,max));
};
constexpr auto empty = cpp2::lambda_wrapper([](std::span<const int> x){
return std::empty(x);
});
main: () -> int = {
i := 15;
if i is less_then(20) {
std::cout << "less than 20" << std::endl;
}
if i is in(10,30) {
std::cout << "i is between 10 and 30" << std::endl;
}
std::cout << inspect i -> std::string {
is less_then(10) = "i less than 10";
is in(11,20) = "i is between 11 and 20";
is _ = "i is out of our interest";
} << std::endl;
v : std::vector<int> = ();
if v is empty {
std::cout << "v is empty" << std::endl;
}
}
The feature is less generic but still useful.
Rebase to main. Make is()
function constexpr
Might require https://github.com/hsutter/cppfront/pull/86 for some use cases.
Might require https://github.com/hsutter/cppfront/pull/86 for some use cases.
Should these be merged? Should all the is
/as
PRs be merged (also #79, #106, #108)? (Not suggesting, just asking. As I look through them I'm trying to grok the overlap.)
It could be merged. I was not sure if you would be willing to merge all functionalities.
Currently, I am not able to allow generic lambda use as I don't know how to write requires clause for lambda_wrapper that will match them.
Can you give an example of what doesn't work?
The goal was to have each functionality separate.
NP - if they don't conflict I'll plan to look at them individually for now. Thx!
@JohelEGP labda_wrapper
requires
requires { &F::operator(); }
It means that the below code will work in the is
statement:
constexpr auto in = [](auto min, auto max) {
return cpp2::lambda_wrapper([](int x, std::pair<int,int> capture) { // <--- explicit type int
return std::get<0>(capture) <= x && x <= std::get<1>(capture);
}, std::pair(min,max));
};
But this will not:
constexpr auto in = [](auto min, auto max) {
return cpp2::lambda_wrapper([](auto x, auto capture) { // <--- generic lambda
return std::get<0>(capture) <= x && x <= std::get<1>(capture);
}, std::pair(min,max));
};
You could move the constraint to the point where you actually have the argument type X
with which to call F
. Then you could use std::invocable<lambda_wrapper<F, Capture>, X>
if the call operator of lambda_wrapper
and F
are likewise appropriately constrained: https://compiler-explorer.com/z/s3cbfccnh.
I will test that direction.
Unfortunately, std::tuple
cannot be used. As I described in the PR description:
lambda_wrapper shall use std::tuple to store arguments that shall go to capture list but std::tuple is not structural type and cannot be used in that context. There are possible workarounds (https://stackoverflow.com/questions/69194075/structural-tuple-like-wrapping-a-variadic-set-of-template-parameters-values-in) but I decided to propose something simple.
Thanks! Implemented as a separate commit along the lines discussed in #79.
@hsutter, your change enables using lambda in inspect expressions but not in if statements. My solution worked for both cases ;)
#include <iostream>
#include <vector>
constexpr auto less_then = [](int value) {
return [=](auto x) { return x < value;};
};
constexpr auto in = [](auto min, auto max) {
return [=](auto x) {
return min <= x && x <= max;
};
};
constexpr auto empty = [](auto&& x){
return std::empty(x);
};
main: () -> int = {
i := 15;
std::cout << inspect i -> std::string {
is (less_then(10)) = "i less than 10"; // works
is (in(11,20)) = "i is between 11 and 20"; // works
is _ = "i is out of our interest";
} << std::endl;
if i is (less_then(20)) { // not working
std::cout << "less than 20" << std::endl;
}
if i is (in(10,30)) { // not working
std::cout << "i is between 10 and 30" << std::endl;
}
v : std::vector<int> = ();
if empty(v) { // works
std::cout << "v is empty" << std::endl;
}
if v is (empty) { // not working
std::cout << "v is empty" << std::endl;
}
}
Ah, you noticed that. :) That was next on my to-do list, and I just checked it in... the above code now works with commit 327ceebeccdc0619c12d03da69df7ecf0c622502 .