Skip to content
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

Rework of is that adds new functionalities or simplify implementation #701

Closed
wants to merge 5 commits into from

Conversation

filipsajdak
Copy link
Contributor

@filipsajdak filipsajdak commented Sep 24, 2023

This change reworks a big part of the code for is.

  • added concepts for simplify and utilize concept subsumption rules,
  • Added type_find_if to iterate over variant types to simplify is for variant,
  • rewrite is overloads to use concepts instead of type_traits (to utilize concepts subsumption rules),
  • remove operator_is, implementation of is for variants now use type_find_if,
  • is that is not using argument x for inspection returns std::true_type or std::false_type
  • add many atomic concepts that were used to build complex concepts - that are required for concept subsumption rules,

Closes #689
Closes #669

Cases handled by the new is():

  • type is type,
  • type is template,
  • type is type_trait,
  • type is concept, (no cpp2 syntax)
  • variable is template,
  • variable is type, (also works for variant, any, optional)
  • variable is value, (also works for variant, any, optional)
  • variable is empty, (also works for variant, any, optional)
  • variable is type_trait,
  • variable is predicate, (works also with free function and generic functions; forbids implicit cast of arguments)

Tests

  • All tests pass,
  • Added tests that present all use cases handled by this change

Issue

  • does not compile on macos-11, clang++, c++20 (one of the CI targets)

The original PR contained a rework of as() and to_string() - they will be provided in separate PRs.

@JohelEGP
Copy link
Contributor

  • add possibility to check if object fulfills concept by using x is (:<T:std::integral>() = {}) syntax,

See #575 (comment):

That isn't yet supported, but my intent is to spell it as <T: type is std::regular>.

@filipsajdak
Copy link
Contributor Author

  • add possibility to check if object fulfills concept by using x is (:<T:std::integral>() = {}) syntax,

See #575 (comment):

That isn't yet supported, but my intent is to spell it as <T: type is std::regular>.

Thank you. @hsutter propose the syntax on cpp2 side. I have prepared the implementation on C++20 side, and the intention is to be able to use it e.g. in if or inspect (assuming <concept> will be syntax for using in such cases - similarly as we are using (value) with comparing to values or to predicates):

inspect x -> std::string {
  is <std::integral> = "x is integral type";
  is <std::floating_point> = "x is floating point type";
  is _ = "other type";
}

@JohelEGP
Copy link
Contributor

If <> will be used to disambiguate concepts, shouldn't
x is (:<T:std::integral>() = {}) be
x is (:<T:<std::integral>>() = {})?

@filipsajdak
Copy link
Contributor Author

I don't know. I know that on cppfront, we don't know if something is a concept or a type (at least, I did not find a way to distinguish that on the C++ side). e.g. in x is T, currently, T can be a type or a template - it will be distinguished on the C++ side using overloads is<T>(x)... I did not find a way to make it work when T is a concept.

@JohelEGP
Copy link
Contributor

Yeah, there's no way.
You can only specialize it to get a prvalue bool back.
You can't use it any other way, not even in a requires-expression.

@filipsajdak
Copy link
Contributor Author

In the cases you mentioned, I guess that we don't need additional <> to distinguish that as type and concept are correct in this context.

@JohelEGP
Copy link
Contributor

I think I understand now.
You're relying on an accidental feature.
In that case, T is the name of a NTTP.
The lowered syntax happens to work for Cpp1 terse concept syntax.
See my reply to Herb's reply: #575 (comment).

@filipsajdak
Copy link
Contributor Author

I was interested in making C++ code work. Based on that I was able to call it from cpp2.

Copy link
Contributor

@JohelEGP JohelEGP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is preexisting, but I have previously commented at https://github.com/hsutter/cppfront/pull/529/files/06d8e5f1229454b6b1a1a392be00d887b03d5e0c#diff-9b9f6d5ded63da9a82c7f23fde117cd494a355abad3624569e71a7bf73f974deR81-R82:

      // Like in P2392, the cases of the built-in `is` should be a chain of conditions.
      // Using overloads to implement that is tedious and error-prone.

I think that has more potential for a simpler implementation.
I wouldn't be surprised if the use of non-concepts or negative constraints dropped to zero.

include/cpp2util.h Outdated Show resolved Hide resolved
@@ -1053,15 +1229,16 @@ inline constexpr auto program_violates_type_safety_guarantee = sizeof...(Ts) < 0

// For literals we can check for safe 'narrowing' at a compile time (e.g., 1 as std::size_t)
template< typename C, auto x >
inline constexpr bool is_castable_v =
concept castable = requires{ C{x}; } || (
std::is_integral_v<C> &&
std::is_integral_v<CPP2_TYPEOF(x)> &&
!(static_cast<CPP2_TYPEOF(x)>(static_cast<C>(x)) != x ||
(
(std::is_signed_v<C> != std::is_signed_v<CPP2_TYPEOF(x)>) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(std::is_signed_v<C> != std::is_signed_v<CPP2_TYPEOF(x)>) &&
different_sign_types<C, CPP2_TYPEOF(x)> &&

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yes... I was reminding myself to correct this line.

regression-tests/mixed-as-for-variant-20-types.cpp2 Outdated Show resolved Hide resolved
regression-tests/mixed-overview-of-as-casts.cpp2 Outdated Show resolved Hide resolved
@JohelEGP
Copy link
Contributor

JohelEGP commented Sep 25, 2023

I was interested in making C++ code work. Based on that I was able to call it from cpp2.

Yeah, the functionality works fine.
It works if the lambda was declared with Cpp1,
and given :<T> () requires std::integral<T> = {}, too.

@filipsajdak
Copy link
Contributor Author

I found errors in my use of concepts (in terms of benefit from subsumption rules). I will update it soon.

@filipsajdak filipsajdak force-pushed the fsajdak-refactor-of-is branch from 58d6ac7 to f10b9fc Compare September 25, 2023 15:01
@JohelEGP
Copy link
Contributor

JohelEGP commented Sep 26, 2023

inspect x -> std::string {
  is <std::integral> = "x is integral type";
  is <std::floating_point> = "x is floating point type";
  is _ = "other type";
}

Although it's in a different context,
note that I'm already using <T> to mean the natural thing,
a template template parameter:
https://github.com/hsutter/cppfront/pull/603/files#diff-1c3784de2023bbe4a493b11b40cad588b19a1ca2219eaeb42589910b99ea3effR1.

@filipsajdak
Copy link
Contributor Author

inspect x -> std::string {
  is <std::integral> = "x is integral type";
  is <std::floating_point> = "x is floating point type";
  is _ = "other type";
}

Although it's in a different context,
note that I'm already using <T> to mean the natural thing,
a template template parameter:
https://github.com/hsutter/cppfront/pull/603/files#diff-1c3784de2023bbe4a493b11b40cad588b19a1ca2219eaeb42589910b99ea3effR1.

Cool! I will take a look at that.

Meanwhile, I am working on correcting this PR, as I found some things that could be improved...

include/cpp2util.h Outdated Show resolved Hide resolved
@filipsajdak filipsajdak force-pushed the fsajdak-refactor-of-is branch from 4dfd8df to f82a67b Compare October 2, 2023 11:23
Copy link
Contributor

@JohelEGP JohelEGP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goes a long way to addressing #492.
It falls short due to:

Once operator is is constrained or answered statically,
it becomes necessary to fix inspect
to omit alternatives whose conditions are non-viable.

Which doesn't seem to be a problem here yet.
That may be due to is overloads like for std::variant,
which still returns bool even though sometimes it's always false.
I argue that, ideally, that'd be constrained so as
to get a compile-time error outside inspect.

// and "as std::string" for the same cases
//
template <typename T>
concept has_to_string_overload = requires{ static_cast<std::string (*)(T const&)>(&to_string); };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's this overload:

inline auto to_string(std::string const& s) -> std::string const&
{
    return s;
}

I don't think std::string satisfies has_to_string_overload.

Anyways, the fact that it has a different return type has always tingled my spider senses.
I've always thought it'd be easy to inadvertently get a dangling reference in a generic context.

There's also these overloads:

inline auto to_string(bool b) -> std::string
{
    return b ? "true" : "false";
}

inline auto to_string(char const* s) -> std::string
{
    return std::string{s};
}

Those would also probably fail due to the argument not being a const&.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will check these... all my tests pass. Nevertheless, I will double-check these.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like only the last one doesn't pass: https://cpp2.godbolt.org/z/871q7P5W7.
I wonder why the others do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cpp2::has_to_string_overload<bool> probably passes thanks to

template<typename T>
inline auto to_string(T const& t) -> std::string
    requires requires { std::to_string(t); }
{
    return std::to_string(t);
}

As that's valid: https://cpp2.godbolt.org/z/4vT5dPcP9.

Copy link
Contributor

@JohelEGP JohelEGP Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cpp2::has_to_string_overload<std::string> passes thanks to

template<typename T>
inline auto to_string(T const& sv) -> std::string
    requires (std::is_convertible_v<T, std::string_view> 
              && !std::is_convertible_v<T, const char*>)
{
    return std::string{sv};
}

See https://cpp2.godbolt.org/z/noMaPWvP5.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes no sense.
Actually calling it chooses the overload returning std::string const&:
https://cpp2.godbolt.org/z/YzPf9ovjv.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were right. I made a mistake here. I put the corrected version here: https://godbolt.org/z/WbGqGnEY1

the const char* was matching the as(bool x) and there were more issues... I believe I have solved them... I will run the tests after Bjarne's talk.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I have fixed that.

include/cpp2util.h Outdated Show resolved Hide resolved
@filipsajdak filipsajdak force-pushed the fsajdak-refactor-of-is branch from 426e833 to 73a1983 Compare October 3, 2023 00:11
@@ -13,7 +13,7 @@ class Square : public Shape { };
print: ( msg: std::string, x: _ ) =
std::cout << msg << x << "\n";

print: ( msg: std::string, b: bool ) =
print: <B:std::convertible_to<bool>> ( msg: std::string, copy b: B ) =
Copy link
Contributor

@JohelEGP JohelEGP Oct 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, what I did at https://github.com/hsutter/cppfront/pull/529/files#diff-508db04ed7e2433d79b037f5473ae5ca5330b4b04b842136ad0c95a40142fee8
was to remove this function and preface main with std::cout << std::boolalpha;.

A more recent alternative to std::cout << std::boolalpha;
might be to change x above to x as std::string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just thought that two versions of the print functions were to show that the result will match the one with bool. If it is not needed then your version is the right one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd have to ask @hsutter for the purpose.

@filipsajdak
Copy link
Contributor Author

I have reworked a lot during my preparation for the talk on cppcon. After the talk, I have done even more... I am changing this PR to the Draft for a moment as I will clean it up. The current status is that it can handle many more cases and switches to perfect forwarding.

I will be traveling today, so I may manage to clean it up.

@filipsajdak filipsajdak marked this pull request as draft October 7, 2023 13:53
Copy link
Contributor

@JohelEGP JohelEGP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This many overloads, constraints, and negative constrains, really is nightmare fuel.
I really think we should tame these overloads with more if constexpr instead of overloads.

For example, rather than one overload with first template parameter std::same_as<std::string>
for each kind of function argument,
e.g.,
std::same_as<std::any> const&

typename X>
    requires has_to_string_overload<std::remove_cvref_t<X>>
            && (!std::same_as<std::remove_cvref_t<X>, std::any>)
            && (!std::same_as<std::remove_cvref_t<X>, std::string>)
auto as( X&& x )
typename X>
    requires pointer_like<std::remove_cvref_t<X>>
auto as( X&& x )
typename X>
    requires pointer_like<std::remove_cvref_t<X>>
             && std::same_as<typename X::element_type, std::string>
auto as( X&& x )
typename X>
    requires (!has_to_string_overload<std::remove_cvref_t<X>>)
            && (!std::same_as<std::remove_cvref_t<X>, std::string>)
            && (!pointer_like<std::remove_cvref_t<X>>)
auto as( X&& )

have one single overload with first template parameter std::same_as<std::string>,
and factor in the overloads' function parameters, like

template <std::same_as<std::string> C, typename X>
auto as( X&& x ) -> decltype(auto) {
    // No `std::same_as<std::any> const&`,
    // instead rewrite is as `cpp2::op_as` to further tame the big `as` overload.
    if constexpr (requires { op_as<C>(std::forward<X>(x)); }) {
        return op_as<C>(std::forward<X>(x)); // `op_as` for `::std` types need to before these `as` overloads.
    }
    else if constexpr (has_to_string_overload<std::remove_cvref_t<X>>
            && (!std::same_as<std::remove_cvref_t<X>, std::any>)
            && (!std::same_as<std::remove_cvref_t<X>, std::string>)) {
        return to_string(x);
    }
    else if constexpr (pointer_like<std::remove_cvref_t<X>>) {
        if (x) {
            return as<C>(forward_like<X>(*x));
        }
        return std::string("(empty)");
    }
    else if constexpr (pointer_like<std::remove_cvref_t<X>>
             && std::same_as<typename X::element_type, std::string>) {
        if (x) {
            return as<C>(forward_like<X>(*x));
        }
        return std::string("(empty)");
    }
    else if constexpr ((!has_to_string_overload<std::remove_cvref_t<X>>)
            && (!std::same_as<std::remove_cvref_t<X>, std::string>)
            && (!pointer_like<std::remove_cvref_t<X>>)) {
        return nonesuch_<casting_errors::no_to_string_cast>{};
    }
}

Then it becomes much easier
to see what's going on,
to simplify by removing negative requirements handled by earlier branches,
and possibly more good stuff.

constexpr inline nonesuch_<> nonesuch;

template <typename X>
concept nonesuch_specialization = requires (std::remove_cvref_t<X> x) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
concept nonesuch_specialization = requires (std::remove_cvref_t<X> x) {
concept nonesuch_specialization = requires (X x) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will check that... I have started to put std::remove_cvref_t<X> in a lot of the places as I was switching to perfect forwarding... and all type traits stop to work.

}

template <typename T>
using pointee_t = std::remove_cvref_t<decltype(*std::declval<std::remove_cvref_t<T>>())>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you want std::iter_value_t<T>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pointee_t is also used for things like std::optional, smart pointers, etc... in general, anything you can dereference.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 1598 to 1599
concept specialization_of_template = requires (std::remove_cvref_t<X> x) {
{ is<C>(std::forward<X>(x)) } -> std::same_as<std::true_type>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
concept specialization_of_template = requires (std::remove_cvref_t<X> x) {
{ is<C>(std::forward<X>(x)) } -> std::same_as<std::true_type>;
concept specialization_of_template = requires {
{ is<C>(std::declval<X>()) } -> std::same_as<std::true_type>;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most probably std::remove_cvref_t<X> and std::forward<X>(x) are not needed. I have been wondering what is better: use std::declval<X>() or requires(X x) { ... }. The second option is more compelling to me (especially after writing many concepts.

I am continue to clean this code up.

// else
return as<C>(CPP2_FORWARD(x));
program_violates_type_safety_guarantee<C, X>,
"No cpp2::to_string overload exists for this type!");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this violated?
There's a catch-all overload inline auto to_string(...) -> std::string.

Copy link
Contributor Author

@filipsajdak filipsajdak Oct 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is right. There is a catch-all overload. That works for cpp2::to_string but is incompatible with the behavior of as when the cast is impossible.

The contract for as is that when you call it, you either have the correct output or an error (static_assert when possible and an exception on runtime).

Having as silently return "(customize me - no cpp2::to_string overload exists for this type)" breaks that contract. We can detect that on compile time, so I think static_assert is the right choice.

A similar thing will happen when you try to cast int to size_t (size_t cannot represent all values of int, so you will get static_assert with a suggestion to use unsafe_narrow<T>()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that choosing that overload is wrong.
It's good to know that using as std::string doesn't choose it.
I guess it works this way because the concept that checks for the overload checks for conversion to function pointer, and the (...) parameter list doesn't work with those.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It does not cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is why I introduce this concept.

@filipsajdak
Copy link
Contributor Author

Regarding many overloads. It is my work-in-progress thing, and I want to clean it up. Having multiple overloads allows us to create methods that will return std::true_type/std::false_type in parallel with methods that return other values.

As we want to allow for the custom operator as, and operator is, I started to worry that having a catch them all method will make that harder. I also want to avoid situations when I need to change old functions when I am adding a new cast.

@filipsajdak
Copy link
Contributor Author

I am preparing a lot of test cases as it is really hard to spot all those errors. I have also found some errors that were silently there but changing the approach made them errors.

@JohelEGP
Copy link
Contributor

JohelEGP commented Oct 7, 2023

Having multiple overloads allows us to create methods that will return std::true_type/std::false_type in parallel with methods that return other values.

If a single branch is chosen, there's no chance for ambiguity in the return type.

@JohelEGP
Copy link
Contributor

JohelEGP commented Oct 7, 2023

I also want to avoid situations when I need to change old functions when I am adding a new cast.

I think it's easier to add new a branch than a new overload.

@filipsajdak filipsajdak force-pushed the fsajdak-refactor-of-is branch from 3988e31 to 4e260fa Compare December 16, 2023 19:18
@filipsajdak
Copy link
Contributor Author

filipsajdak commented Dec 17, 2023

@hsutter I am fixing is() overloads - prototype available here: https://godbolt.org/z/Gr1shc9YG (I needed to change a couple of other things to improve it). Sorry for the delay... I am fixing the implementation and will align the tests with tests done in Compiler Explorer. I will probably need to do the same for as(). Sorry for the inconvenience - I made some refactorings previously, and it seems I did not test it properly (I am fixing it now)/

@hsutter
Copy link
Owner

hsutter commented Dec 17, 2023

No worries, I appreciate all the thought and care you're putting into this.

@filipsajdak
Copy link
Contributor Author

I have fix is() cases and I am working on as() cases.

@filipsajdak filipsajdak force-pushed the fsajdak-refactor-of-is branch from 35a70b8 to 4f15948 Compare January 6, 2024 23:38
@filipsajdak
Copy link
Contributor Author

@hsutter I moved the implementation forward. Most of the cases are working... I see that CI checks failed on macOS-11 and macOS-latest. I just realized the CPU architecture is the main difference between my platform and the one used on the CI side.

CI uses x86_64, and I am using arm64. Maybe it will be better to have separate regression tests for apple-clang-14, one for x88_64 and one for arm64. I am still adding test cases and I am trying to fix all found issues and clean up the implementation.

@hsutter
Copy link
Owner

hsutter commented Jan 7, 2024

CI uses x86_64, and I am using arm64. Maybe it will be better to have separate regression tests for apple-clang-14, one for x88_64 and one for arm64.

Sounds good -- I'm not the expert there so I'd welcome anyone to provide a PR to add that. Thanks!

@filipsajdak filipsajdak changed the title Rework of is and as that adds new functionalities or simplify implementation Rework of is that adds new functionalities or simplify implementation Jan 31, 2024
@filipsajdak
Copy link
Contributor Author

@hsutter, I started splitting this big PR into smaller ones. I believe this one covers the is() refactor and can be reviewed and hopefully merged.

I am sorry for the late delivery... life.

@filipsajdak
Copy link
Contributor Author

Wait a sec... sth... is wrong with this PR... fixing.

@filipsajdak filipsajdak force-pushed the fsajdak-refactor-of-is branch from f161425 to b88b154 Compare February 1, 2024 00:14
@filipsajdak
Copy link
Contributor Author

@hsutter ok, now fixed. I messed up with branches. But now it is ready.

@hsutter
Copy link
Owner

hsutter commented Feb 1, 2024

Thanks! I'm just looking at merging #956 which @JohelEGP mentioned should go first -- not sure it that will require a rebase, of this, but should be just a rebase?

@filipsajdak
Copy link
Contributor Author

@hsutter @JohelEGP I am looking at change #956, which only brings replacing multiple is() and as() overloads and replaces them with one is() and one as() functions and multiple if constexprs inside them.

My implementation of is() introduces new functions using function overload (as that was the pattern previously used by Herb). I searched for some rules that might guide me here. I have found one that supports @JohelEGP way: isocpp/CppCoreGuidelines#2099

I am not insisting on choosing my current solution. Let's agree on one way of implementing this kind of function. I will adjust my implementation to follow what will be decided. I have been worried about having functions with multiple constexpr ifs, but maybe it is just me.

Opinions?

I really want to deliver it as it has not been merged for a long time, and it is troublesome to keep it in a mergeable state.

@JohelEGP
Copy link
Contributor

JohelEGP commented Feb 7, 2024

A chain of constexpr if is worrisome indeed, but less so than overloading.

The next step would be to rewrite non-built functions as op_is/op_as
and let the built-ins forward to those user-defined overloads
(including the ones for std:: types in cpp2::).

For example, you add this is-type overload:

template< typename C, can_bound_to<C> X >
    requires not_same_as<X, std::any>
constexpr auto is( X && ) -> std::true_type {
    return {};
}

That requires is necessary because everything is named is.

The next one, with requires (!std::derived_from<std::remove_cvref_t<X>, C>),
can be avoided thanks to the definite order if a chain of if.
Same for requires cannot_bound_to<X, C>.
The other one will also be easy to review
since it's just an addition to the chain.

Only the first one can't be integrated into the chain.
You can write it as an overload
until I get around the next rewrite for op_is/op_as.

@hsutter
Copy link
Owner

hsutter commented Mar 8, 2024

I really want to deliver it as it has not been merged for a long time, and it is troublesome to keep it in a mergeable state.

Again, thanks for this and for waiting. I've been watching to see when I should review this -- I got the impression you were making one more change with the last discussion above, but maybe I misunderstood? Sorry if I missed that it's ready, please just let me know when it's ready to review and mergeable and I'll look at it next.

@filipsajdak
Copy link
Contributor Author

@hsutter I am closing this... next merge request ongoing.

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.

[BUG] e is func is false
5 participants