Passing Chaiscript function to C++


#1

Is there a way to pass a chaiscript function to a C++ function that accepts an std::function? I know you can get a std::function from chaiscript, but pass one from Chaiscript to a C++ function.

Type * processList(const std::function<bool(Type*)> & f) const;
engine->add(chaiscript::fun(&processList), "processList")

error C2672: ‘chaiscript::fun’: no matching overloaded function found

Kind of those weird things I think if from C++ you can make a std::function that it should work that way too from Chaiscript.


Return_Value exception
#2

Yes, passing functions directly between ChaiScript and C++ is one of ChaiScript’s unique strengths.

I’m going to need a more complete example to help with this error, but based on what you show, I can make a guess.

Your function processList has a const qualifier after it. const qualifiers are only allowed on member functions, so I’m guessing it’s a member function.

If it’s a member function, then you are missing the class in your call to fun() which should be something like chaiscript::fun(&MyClass::processList).

Does this help?

-Jason


#3

Yes it was a member function and it had the MyClass:: part I just copied it wrong.

What I found out was it wasn’t the std::function but that I had an internal (private) function with the same name with different arguments.

class TheClass 
{
public:
    void doSomething(int one);
private:
    void doSomething(int one, int two, int three);
};

So when you do
engine->add(chaiscript::fun(&TheClass::doSomething), "doSomething")
It gives a bunch of unhelpful errors that never really told me it was “ambiguous”.

Not sure if this is a bug with MSVC because it shouldn’t be ambiguous, that other function is in the private section and it’s outside the class.


#4

Not a bug in MSVC, part of the C++ standard. Accessibility is not resolved until after name lookup.

You will need to either wrap the function in a lambda or do a static cast to resolve the specific version you want yourself.

See here for example: https://github.com/ChaiScript/ChaiScript/blob/develop/cheatsheet.md#with-overloads

-Jason


#5

Interesting.

I still have one more issue with this and that is a function that accepts a pointer as an argument.

Object * ProcessGlobalTypeList(unsigned char type, const std::function<bool(Object*)> & f);
engine->add(chaiscript::fun(&MyClass::ProcessGlobalTypeList, &MyClassObj), "ProcessList");

Now in Chaiscript if I make a script

def ProcessEffects(Object obj) 
{	
     // Do Something
}
ProcessList(0, ProcessEffects); // Takes a byte and a function with a pointer param.

I get this error.
Error: “Error with function dispatch with function ‘ProcessList’” With parameters: (const int, const Function) during evaluation at (137, 2)

I’m assuming it’s because Object in the function parameter is being passed in as a pointer, but I could also be doing it wrong.


#6

I put together a 100% working version here, since I don’t know all of the details of what you have implemented.

I’m guessing there are two issues. The first is that you did not tell ChaiScript the name of the type “Object” so when you wrote the function ProcessEffects(Object obj) it didn’t know what “Object” meant.

Which… you would have gotten a reasonable warning about except for the fact that “Object” is already a built in type in ChaiScript, so it was expecting you to call a function that took a ChaiScript::Dynamic_Object type.

#include <chaiscript/chaiscript.hpp>

struct MyObject  { int val = 0; };

MyObject * ProcessGlobalTypeList(unsigned char type, const std::function<bool(MyObject*)> & f)
{
  MyObject o{42};
  f(&o);
}

int main()
{
  chaiscript::ChaiScript engine;
  engine.add(chaiscript::fun(ProcessGlobalTypeList), "ProcessList");
  engine.add(chaiscript::fun(&MyObject::val), "val");
  engine.add(chaiscript::user_type<MyObject>(), "MyObject");

  engine.eval(R"chaiscript(

def ProcessEffects(MyObject obj) 
{
  print("ProcessEffects called with val: ${obj.val}");
  // Do Something
  return true;
}

ProcessList(0, ProcessEffects); // Takes a byte and a function with a pointer param.

  )chaiscript");

}

-Jason


#7

Oh i’m sorry I relooked at whats going on and it IS working for the first call only.

Lets say that the function is like this (a list like the name says)

MyObject * ProcessGlobalTypeList(unsigned char type, const std::function<bool(MyObject*)> & f)
{
  MyObject o{42};
  for (int i = 0; i < 2000; i++)
       f(&o);
}

After the second call on my machine I get this.

def reinit(StringVector sv, IntVector iv) 
{

}

def deinit(StringVector sv, IntVector iv) 
{	

}

def ProcessThing(Thing t) 
{
	log("Working! " + t.ThingNum.to_string() + "\n");
}

def main() 
{
	ProcessGlobalUsedList(ProcessThing);
}

Script Loaded successfully!
Working! 2658 (First call)
Error: “Error with function dispatch with function ‘ProcessGlobalTypeList’” With parameters: (const int, const Function) during evaluation at (18, 2)

That should print out all the objects idxs on the map (game engine), but it prints out one then errors.

I designed a test using your example which mimics how the engine works and strangely it works fine.

#include <chaiscript/chaiscript.hpp>
#include <chaiscript/chaiscript_stdlib.hpp>

struct MyObject { int val = 0; };
MyObject o[2000];


MyObject * ProcessGlobalTypeList(unsigned char type, const std::function<bool(MyObject*)> & f)
{
    for (int i = 0; i < 2000; i++) // some large list
        f(&o[i]);
    return nullptr;
}

int main()
{
    for (int i = 0; i < 2000; i++)
        o[i].val = i;

    chaiscript::ChaiScript engine(chaiscript::Std_Lib::library());
    engine.add(chaiscript::fun(ProcessGlobalTypeList), "ProcessList");
    engine.add(chaiscript::fun(&MyObject::val), "val");
    engine.add(chaiscript::user_type<MyObject>(), "MyObject");

    engine.eval(R"chaiscript(

def ProcessEffects(MyObject obj) 
{
  print("ProcessEffects called with val: ${obj.val}");
  // Do Something
  return true;
}

def main() 
{
ProcessList(0, ProcessEffects); // Takes a byte and a function with a pointer param.
}

  )chaiscript");

    auto func = engine.eval<std::function<void()>>("main");
    while (true) 
    {
        // Game Engine
        func();
    }

}

I’ll have to look into whats going on more.


#8

Ok I figured it out. When the game tries to call
const std::function<bool(Thing*)> & f

An internal chaiscript exception occurs.

  catch(const exception::bad_boxed_cast &){
    try {
      Const_Proxy_Function f = t_ss->boxed_cast<const Const_Proxy_Function &>(fn);
      // handle the case where there is only 1 function to try to call and dispatch fails on it
      throw exception::eval_error("Error calling function '" + this->children[0]->text + "'", params, {f}, false, *t_ss);
    } catch (const exception::bad_boxed_cast &) {
      throw exception::eval_error("'" + this->children[0]->pretty_print() + "' does not evaluate to a function.");
    }
  }

Text = ProcessGlobalUsedList

The error occurs at this point in the code

MyObject * ProcessGlobalTypeList(unsigned char type, const std::function<bool(MyObject*)> & f)
{
  MyObject o{42};
  for (int i = 0; i < 2000; i++)
       **f(&o); <------------------------------------------------------------------------------------**
}

So it’s not an issue with calling the function inital function, but when that function calls the chaiscript function.

I’ve traced it to this section of Chaiscript is where the exception is being thrown.


Basically what happens is it calls ProcessGlobalTypeList ONCE, then throws an exception afterwards.

Working! 2755
Error: “Error calling function ‘ProcessGlobalUsedList’” With parameters: (const Function) during evaluation at (scripts\test.chai 19, 2)

Expected: (const class std::function<bool __cdecl(struct Thing *)>)

scripts\test.chai (19, 2) ‘ProcessGlobalUsedList(ProcessThing)’


#9

Actually,

Disregard I just realised the problem. std::function expects a return value.

It’s not

def ProcessThing(Thing t) 
{
	log("Working! " + t.ThingNum.to_string() + "\n");
}

It’s

def ProcessThing(Thing t) 
{
	log("Working! " + t.ThingNum.to_string() + "\n");
    return true;
}

Requires a return bool.

Thanks for the help though. I’m an idiot.

I absolutely love how well this scripting engine fits with our code. Other than fighting internally with myself its been a really smooth setup!


#10

Glad you got it sorted out. I noticed in your little snippet several messages ago that you were not returning a value from your function, so I explicitly returned one in my example, since you had defined your function object as bool (Thing).

I don’t know if I can capture that in a way that would provide a better error message or not - something along the lines of “unable to convert return value to expected type” or something would be necessary.

I created an issue to track this enhancement https://github.com/ChaiScript/ChaiScript/issues/304

-Jason