Skip to content

[SUGGESTION] Add possibility of using lambda in is-statements (inspect and if statements) #90

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

filipsajdak
Copy link
Contributor

@filipsajdak filipsajdak commented Oct 23, 2022

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;

@filipsajdak filipsajdak force-pushed the fsajdak-alternatives-with-lambda branch from 48dbcb3 to aef3217 Compare October 23, 2022 16:21
@filipsajdak filipsajdak marked this pull request as draft October 23, 2022 17:44
@filipsajdak
Copy link
Contributor Author

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.

@filipsajdak
Copy link
Contributor Author

On the last push I have added support for using id-expression ( expression-list ) as alternative in the inspect expression.

@filipsajdak filipsajdak marked this pull request as ready for review October 23, 2022 20:02
@filipsajdak filipsajdak force-pushed the fsajdak-alternatives-with-lambda branch from c088908 to 4c87da8 Compare October 30, 2022 20:00
@filipsajdak filipsajdak force-pushed the fsajdak-alternatives-with-lambda branch from 848a996 to cfa2b25 Compare November 26, 2022 13:35
@filipsajdak
Copy link
Contributor Author

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.

@filipsajdak filipsajdak force-pushed the fsajdak-alternatives-with-lambda branch from cfa2b25 to a4b33a2 Compare November 29, 2022 21:22
@filipsajdak
Copy link
Contributor Author

Rebase to main. Make is() function constexpr

@filipsajdak
Copy link
Contributor Author

Might require #86 for some use cases.

@hsutter
Copy link
Owner

hsutter commented Dec 17, 2022

Might require #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.)

@filipsajdak
Copy link
Contributor Author

It could be merged. I was not sure if you would be willing to merge all functionalities.

@JohelEGP
Copy link
Contributor

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?

@filipsajdak
Copy link
Contributor Author

The goal was to have each functionality separate.

@hsutter
Copy link
Owner

hsutter commented Dec 17, 2022

NP - if they don't conflict I'll plan to look at them individually for now. Thx!

@filipsajdak
Copy link
Contributor Author

@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));
};

@JohelEGP
Copy link
Contributor

JohelEGP commented Dec 17, 2022

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.

@filipsajdak
Copy link
Contributor Author

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.

@hsutter
Copy link
Owner

hsutter commented Dec 22, 2022

Thanks! Implemented as a separate commit along the lines discussed in #79.

@hsutter hsutter closed this in 29fbd92 Dec 22, 2022
@filipsajdak
Copy link
Contributor Author

@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;
    }
}

hsutter added a commit that referenced this pull request Dec 23, 2022
@hsutter
Copy link
Owner

hsutter commented Dec 23, 2022

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 327ceeb .

@filipsajdak filipsajdak deleted the fsajdak-alternatives-with-lambda branch January 12, 2023 21:22
Azmah-Bad pushed a commit to Azmah-Bad/cppfront that referenced this pull request Feb 24, 2023
Azmah-Bad pushed a commit to Azmah-Bad/cppfront that referenced this pull request Feb 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants