8000 How to test if a member std::function is empty, and how to reset it to nullptr? · Issue #321 · ChaiScript/ChaiScript · GitHub
[go: up one dir, main page]

Skip to content

How to test if a member std::function is empty, and how to reset it to nullptr? #321

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

Open
totalgee opened this issue Jan 22, 2017 · 16 comments

Comments

@totalgee
Copy link
Contributor

This is a question not addressed in the cheatsheet, as far as I can tell... I asked a question two weeks ago on the forum, but haven't seen any responses there -- sorry for the repeat posting.

I have a C++ class member that holds a callback function pointer. It is a std::function, so it's valid for it to be empty (nullptr). I have no problems binding the class in ChaiScript, but I don't know the proper way to test if that member is empty, nor how to set it to nullptr (or 0).

// Example struct with callback member
struct MyClass {
    std::function<float(float)> callbackFunc = {};
};

// After registering MyClass, register the member:
m->add(fun(&MyClass::callbackFunc), "callbackFunc");

In scripting, I am able to create an instance of my class, assign a lambda function to it, and evaluate the function (e.g. using obj.callbackFunc(3.1f)). But I don't know how to check if it's empty, or how to reset it to nullptr (or 0). For the empty (boolean) test, I tried adding a type conversion:

chai.add(type_conversion<decltype(MyClass::callbackFunc), bool>([](decltype(MyClass::callbackFunc) f) { return f != nullptr; }));

That doesn't work, I get a "Condition not boolean" when trying to test the member. For the assignment, I tried various permutations of operator= (taking an int argument, i.e. 0), but haven't yet hit on something that worked.

I can make a specific method to check or reset that member, but I'd rather something that worked "automatically" for any type of std::function variable.

For the emptiness test, I came across this in the docs:

obj.callbackFunc.is_var_null()

but that doesn't seem to work (it returns false whether or not my callbackFunc is set to something), presumably because std::function is an object and not (directly) a pointer...

Any suggestions?

Desired Behaviour

obj = MyClass();
obj.callbackFunc = fun(x) { return x * 13.5f; }; // callbackFunc is a std::function member defined in C++

if (!obj.callbackFunc) {
    print("callback not set");
}
else {
    print(obj.callbackFunc(3.1f));
}
obj.callbackFunc = nullptr; // or "obj.callbackFunc = 0" would be fine
@lefticus
Copy link
Member
lefticus commented Feb 2, 2017

I think you are on the right track with adding your conversion to bool. I recently (0d4a99a) fixed a bug that allowed conversions to bool in conditionals.

Are you using a new enough build to have this fix in it?

@totalgee
Copy link
Contributor Author
totalgee commented Feb 2, 2017

Yes, I'm using b7e8897 from Dec. 29, 2016.

But it doesn't seem to work. Do I have to do something to "force" the cast to a bool? Or in scripting, should I just be able to write if (!thing.callback) ...?

I get:

Exception caught: Error: "Error with prefix operator evaluation: '!'" With parameters: (Function) 

I've tried various variations of type conversions to bool (they compile fine), along the lines of:

chai.add(type_conversion<decltype(MyClass::callbackFunc), bool>([](decltype(MyClass::callbackFunc) f) { return f != nullptr; }));

@lefticus
Copy link
Member
lefticus commented Feb 2, 2017

oh, it might actually be the ! that is throwing it off. Can you easily test without first negating it? swapping the logic of your if/else?

lefticus added a commit that referenced this issue Feb 2, 2017
@totalgee
Copy link
Contributor Author
totalgee commented Feb 2, 2017

Nope, that doesn't work either. In that case, I get:

    if (o.callbackFunc) {  // gives Error: "Condition not boolean"
        print("callback set");
    }
    if (!o.callbackFunc) {  // gives Error: "Error with prefix operator evaluation: '!'" With parameters: (Function) 
        print("callback not set");
    }
    o.callbackFunc = 0; // gives Error: "Unable to find appropriate'=' operator." With parameters: (Function, const int)

That's when I use the following bindings:

    chai.add(fun(&MyClass::callbackFunc), "callbackFunc");
    chai.add(type_conversion<decltype(MyClass::callbackFunc), bool>([](decltype(MyClass::callbackFunc) f) { return f != nullptr; }));  // or return static_cast<bool>(f);
    chai.add(fun([](decltype(MyClass::callbackFunc)& fn, int& p) {
        if (p==0) {
            fn = nullptr;
        }
        else {
            throw(std::runtime_error("Attempt to assign int that's non-zero to a function"));
        }
    }), "=");

My assignment to (for example) 0 doesn't work either... Should I be registering these with my std::function type as the arguments (as I am now), or should I be using Proxy_Function or boxed values or something? (but then I don't know how to get access to my actual function)

@lefticus
Copy link
Member
lefticus commented Feb 2, 2017

I believe we have a combination of things going on. The first is that I don't see any current tests for directly exposing a std::function as the member of a class. So I think that ChaiScript is doing something not quite right there.

Even a simple test of just making a member that is a std::function, then trying to call that fails in an unexpected way. I think this is a consequence of how dynamic objects and their methods are implemented.

The second issue seems to be the way function objects are registered and handled internally, so it's not able to find the conversion operator.

For the moment you will need to handle it through getters and setters kind of things, and registering of callbacks. Something like:

class MyClass 
{
public:
  float callbackFunc(float f) const { return m_callback(f); }
  void setCallbackFunc(const std::function<float(float)> &f) { m_callback = f; }
  bool hasCallback() const { return m_callback != nullptr; }

private:
  std::function<float(float)> m_callback;
};

We'll leave this issue open and I'll see if I can figure out something that can work a bit more naturally.

@totalgee
Copy link
Contributor Author
totalgee commented Feb 2, 2017

Here is an example program (Gist) that illustrates what I'm trying right now, maybe you'll find a way to do what I'm wanting...

Thanks!

@totalgee
Copy link
Contributor Author
totalgee commented Feb 2, 2017

Okay, but in response to your last comment, note that I am able to call my callback fine from scripting...

@lefticus
Copy link
Member
lefticus commented Feb 2, 2017

huh, I was not able to in my tests... interesting. I'll be sure to check what you are doing.

@totalgee
Copy link
Contributor Author
totalgee commented Feb 2, 2017

FYI I was able to call (via scripting) a callback member std::function that had a) been set in C++ (assigning a lambda) or b) set in ChaiScript (assigning a lambda fun).

@totalgee
Copy link
Contributor Author
totalgee commented Feb 3, 2017

(I just updated the Gist to show the success of calling both C++ and ChaiScript-defined callbacks.)

@totalgee
Copy link
Contributor Author

Just revisiting this (a few years later ;-). I was almost able to get what I wanted by exposing a separate ChaiScript function on my function type, to test whether the std::function was "empty" (vs valid/callable).

// (C++)
// After bootstrapping MyClass (which has a member std::function<int(int)> callbackFunc)
chai.add(fun([](decltype(MyClass::callbackFunc) const& f) {
    return !f;
}), "empty");

Then, ideally you'd be able to do:

// (ChaiScript)
auto o = MyClass();
if (o.callbackFun.empty) {
    o.callbackFunc = fun(i) {
                  print("In ChaiScript callback with ${i}");
                  return i + 1;
    };
}

However, this doesn't work either...well, the "empty" test works fine if callbackFunc has been set to something callable, but not if it's still an unintialized std::function. It seems to be called with a non-empty (?) std::function, but one which can't be called. After digging further in the code, I see that ChaiScript deals only with Proxy_Function, which kind of erases types of any "callables" it might hold. So I can also make am empty method that takes Proxy_Function as argument (and it indeed gets called if I don't define a more specific empty method) , but I don't know how to try to cast its argument to my callbackFunc type.

Any other ideas or tips I could try? Thanks.

@totalgee
Copy link
Contributor Author

Okay, the following seems to work for me. Unfortunately it captures all Assignable_Proxy_Functions, not just ones with the arguments and return type I want, but that's not a big deal for me. If I had multiple callback types to support, I could handle them all in this single "empty" method (with a different dynamic_cast testing each callback type). For my liking, it relies a bit too much on knowing the underlying implementation...but at least I've got something for now that works for me. If anyone has a better suggestion I'd be glad to hear it...

chai.add(fun([](dispatch::Assignable_Proxy_Function const& func) {
    auto cb = dynamic_cast<dispatch::Assignable_Proxy_Function_Impl<int(int)> const*>(&func);
    if (cb) return !cb->internal_function(); // return true when internal std::function is empty

    // What to return (or throw) for other proxy function types we might be called with?
    return true;
}), "empty");

@totalgee
Copy link
Contributor Author

In terms of resetting the function to be empty after it's defined, it would help if the method Assignable_Proxy_Function_Impl::internal_function() returned a reference to the std::function object (or else had a clear method to reset it).

What do you think about changing this line:

std::function<Func> internal_function() const
{
return m_f.get();
}

to return a reference to the std::function?

        std::function<Func>& internal_function() const
        {
          return m_f.get();
        }

And/or adding a clear() method to the class?

        void clear()
        {
          m_f.get() = std::function<Func>{};
        }

I'd be happy to submit this as a PR, if you'll accept it.

@RobLoach
Copy link
Contributor

That is changing the function signature, so I'm not entirely sure if tests will pass afterwards. But if things are still working, it should be okay.

@schen2000
Copy link

I use nullptr to initialize a std::function variable. Not sure it's correct.

@totalgee
Copy link
Contributor Author

That's correct in C++ (e.g. to reset it; you don't need to initialize a new one), but the issue I was having was in ChaiScript...

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

No branches or pull requests

4 participants
0