-
Notifications
You must be signed in to change notification settings - Fork 260
is()/as(): refactor of is() and as() for variant to new design (part 2 of n) #1203
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
is()/as(): refactor of is() and as() for variant to new design (part 2 of n) #1203
Conversation
6077305
to
6c1d14d
Compare
6c1d14d
to
7924b70
Compare
@hsutter please review it. I am suppressing my willingness to do refactoring and enhancements at once. I understand that this code is not as it should be - I am pushing myself to keep the existing functionalities (I am adding tests that will track the future fixes and functional changes). I need to discuss the approach that |
660b4a5
to
a28dbf9
Compare
Thanks! Hmm, that example does not currently work the way I wanted it to. Here is a complete version I just tried: in: (a, b) :(x) a$ <= x <= b$;
main: () = {
v : std::variant<int, int, std::string> = "xyzzy";
v.emplace<0>(42);
if v is (in(0,100)) {
std::cout << "<0>: in(0,100)"; // this is reached
}
v.emplace<1>(42);
if v is (in(0,100)) {
std::cout << "<1>: in(0,100)"; // this is not
}
} I intended that both of those succeed, because Edited to add: So please feel free to make it do that! |
a28dbf9
to
92df7c6
Compare
@hsutter OK, now this works as expected. Should I also enable the handling of variants with more than 20 types? We can remove the limit and handle as many as variant supports :). |
Thanks!
Sure, if that's straightfoward to do... variadic |
92df7c6
to
ae252e9
Compare
@hsutter OK, they are now generic (is() and as()). I need to sync the tests. |
@hsutter one another question that is important: how "smart" The highest principle is that if We can do a lot of checks with v: std::variant<int,double,std::string> = 42;
if v is int {
f(v as int);
}
o: std::optional<int> = 44;
if o is int {
f(o as int);
}
a: std::any = 100;
if a is int {
f(a as int);
} In these examples, we are extracting the type from a more complex type - that works fine. And here is the question about how "smart" it should be: what is the expected behavior in the following cases: v: std::variant<std::variant<std::variant<int>>> = 42;
if v is int {
f(v as int);
} The intention is clear, and we can unambiguously identify what we need to do to achieve it (and I have implementation that is capable of that). A similar code can be written for I have some thoughts about it:
I have also played a little bit with that approach, and it works but changes the behavior of this feature dramatically. On the positive side, I can add that it simplifies the code of I am looking forward to your point of view. |
Excellent question. This comes up with The principle is: Cpp2 tried to avoid magic, and to make important operations explicit and visible. Therefore, when you unwrap something, always only unwrap once. Otherwise you get semantic surprises and inconsistencies. Example of semantic surprises: Consider Example of inconsistencies: I think it would be bad if Does that make sense? I'm definitely always open to new information and examples, but that's my current thinking about it. Great question, and a common one that comes up a lot! |
Oh, and a thought on this:
I don't think they usually do. First, they are asking |
Because we have special Without these special cases, we could write the following code: //This is the incorrect code - do not use it
func: (x) = {
if x is int {
std::cout << (x + 1) << std::endl;
}
} Because we treat func: (x) = {
if x is int {
std::cout << ((x as int) + 1) << std::endl;
}
} The general rule is "if you check variable with And if we accept that as a rule and the correct way of working, then there is not much difference between extracting If you write the same boilerplate code that checks and extracts the embedded type, then the compiler, knowing the type, can write the same code for you. Let's assume that we write a special function to work with the complex type: func: (x : std::optional<std::variant<*int, *double, *std::string>>) = {
if x.has_value() && x*.index() == 0 && std::get<0>(x*) != nullptr {
std::cout << ((std::get<0>(x*)*) + 1) << std::endl;
}
} I wrote this function based only on its type. Is it correct (assuming I didn't make any mistake in the code)? If the answer is yes, then the generic version of the function can do the same (assuming it will do the recursive job): func: (x) = {
if x is int {
std::cout << ((x as int) + 1) << std::endl;
}
} And it could do even more! E.g. it will handle cases when The same job it will do for the My thinking is based on the assumption that information about the type is enough to write correct code that checks if the extraction is possible and then extracts the type. If it is true, I believe that recursive Taking your example of // This is the incorrect code - do not use it
func: (x) = {
if (x.wait_for(0s) == future_status::ready) && (x.get().wait_for(0s) == future_status::ready) {
std::cout << ((x.get().get()) + 1) << std::endl;
}
} Then, I believe it will be handled correctly. If it is incorrect code (and because I will need to write the tests for that, but as for my current understanding, the generic function func: (x) = {
if x is int {
std::cout << ((x as int) + 1) << std::endl;
}
} will not compile for |
After sleeping on this a bit: I think the recursive "flattening" feature is useful for the dynamic types ( The reason that we extract For the dynamic types ( That principle may hold even for multilevel pointers, because although So yes, okay, let's try it. Thanks! (*) Later, if there are surprises, we could then look at making it opt-in. The solution I'm familiar with in other languages is to have an explicit |
@hsutter OK, I will prepare a separate PR for the recursive "flattening" feature. I have been using it for a while, and the only surprise I had was that I needed to pay more attention to the order of In this one, I will focus on refactoring I need to sync the tests, and the PR will be ready. After this PR, I will do a refactoring for |
282484b
to
7c06774
Compare
992254c
to
f5c3f97
Compare
eb2609c
to
12c8a24
Compare
I will prepare this PR. |
No functional changes The implementation still can handle only 20 types in variant. It will be changed in future Pull Requests.
12c8a24
to
330e225
Compare
def893c
to
6ffde34
Compare
@hsutter, the PR is ready! |
After this PR is merged the next PR to be reviewed and merged is #1251 |
Thanks! |
Currently,
is()
andas()
implementations are done using two redundant approaches - using concepts subsumption rules and usingconstexpr if
s.Thanks to the discussions with @JohelEGP, we moved some of them to the
constexpr if
s. This PR is meant to move all implementations to one version, which will simplify the implementation and make modification of them more manageable.My intention is not to introduce any functional change - only refactoring.
Unfortunately, I have already found a couple of bugs that were accidentally fixed in a new version.
Example:
I will minimize these changes and introduce workarounds to keep the old behavior. To ensure there is no functional change introduced. These workarounds will be removed in the upcoming PRs.
This PR introduces extensive tests that will ensure no functional change is introduced, and that will track future changes of
is()
oras()
implementations.