How to exposed member variable of the parent class in chaiscript?


#1

I have two classes, Base and Derived. I only want to expose Derived to chaiscript but I need to access some of member of Base in chaiscript. This is my attempt :

#include <iostream>
#include <ChaiScript\include\chaiscript\chaiscript.hpp>
#include <ChaiScript\include\chaiscript\chaiscript_stdlib.hpp>
 
using namespace std;
using namespace chaiscript;
 
struct Base
{
	int Data;
};
 
struct Derived : public Base
{
	
}; 
 
int main()
{
	ChaiScript chai(Std_Lib::library());
 
	chai.add(user_type<Derived>(), "Container");
	chai.add(constructor<Derived()>(), "Container");
	chai.add(base_class<Base, Derived>());
	chai.add(fun(&Base::Data), "value");
	try
	{
		chai.eval(R"(					
					var C := Container()
					C.value = 45
				)");
	}
	catch (std::exception& e)
	{
		cout << e.what();
	}	
}

When I try to compile it in VS 2013 I got this error:

1>f:\library\chaiscript\include\chaiscript\dispatchkit\type_conversions.hpp(412): error C2338: Base class must be polymorphic
1>          f:\project\basic\testchai\main.cpp(25) : see reference to function template instantiation 'chaiscript::Type_Conversion chaiscript::base_class<Base,Derived>(void)' being compiled
1>f:\library\chaiscript\include\chaiscript\dispatchkit\type_conversions.hpp(413): error C2338: Derived class must be polymorphic

The funny thing is that in MSDN, VS 2013 doesn’t have error C2338, the last version that has that error is VS 2008 and it got something to do with ATL.


#2

I don’t have a lot of time, so here’s the short answer: If you don’t need anything from Base class exposed, then don’t expose anything.

This should work:

chai.add(fun(&Derived::Data), "value");

I’m assuming it will compile with no problem, but there’s the potential that it will not be accessible at runtime because ChaiScript will still see it as a member of Base. I forget the exact rules here (it’s a C++ question, not a ChaiScript question).

If so, then you can coerce the compiler into doing what you want like this:

chai.add(fun(static_cast<int (Derived::*)>(&Derived::Data)), "value");

I’ll fill in more details about the specific error you were getting later.

-Jason


#3

I already tried

chai.add(fun(&Derived::Data), "value");

and it result in runtime error

"Error with function dispactch for function 'value'" With parameters: (Container).()

Your second suggestion works fine, though I need to delete line

chai.add(base_class<Base, Derived>());

or I get the same compile error.

My problem is solved but I still like to hear the detail explanation of what’s going on if you have the time.


#4

So, the compile time error you were getting is this one:

Basically, to call base_class, the types have to be “polymorphic” which means that you need a vtable. vtables are created by the compiler when a virtual function is involved. A vtable is also necessary to be able to do runtime type conversion with the dynamic_cast<> feature of C++.

  • I cannot do runtime type conversion via dynamic_cast without a vtable
  • You don’t have a vtable without virtual methods
  • therefor base_class registration failed

I hope that makes some sense.


#5

That means the example base_class in chaiscript doc doesn’t compile. So I try to paste this code from doc

class Base {};
class Derived : public Base {};
void myfunction(Base *b);

int main()
{
  chaiscript::ChaiScript chai;
  chai.add(chaiscript::base_class<Base, Derived>());
  Derived d;
  chai.add(chaiscript::var(&d), "d");
  chai.add(chaiscript::fun(&myfunction), "myfunction");
  chai("myfunction(d)");
}

and yes, I get the same compile error.

Why the needs of dynamic_cast ? I thought dynamic_cast only needed if you want to convert from Base to Derived, but the code above try to convert Derived to Base that should be automatic.
Virtual function is not too popular these days and it will be better if chaiscript doesn’t require the class to be polymorphic to be able to be used in chaiscript.


#6

Good point on the doc. I need to fix it or something.

Here’s the issue, say you do this:

class Base { virtual ~Base() = default; };
class Derived : public Base { 
  public:
    void someFunction();
};

int main()
{
  chaiscript::ChaiScript chai;
  chai.add(chaiscript::base_class<Base, Derived>());
  chai.add(chaiscript::fun(&Derived::someFunction), "someFunction");
  std::shared_ptr<Base> d = std::make_shared<Derived>();
  chai.add(chaiscript::var(d), "d");
  chai("d.someFunction()"); // failure
}

d in the example above is an object of type Derived but it’s a pointer to type Base. To account for this potential usage, we automatically register both Derived->Base and Base->Derived dispatches and call the most derived version of a function appropriately. This is why we require the types to be polymorphic to support the base_class<> registration.

dynamic_cast<> from Base->Derived must be used if there is virtual inheritance involved. It seems to be rather difficult to know for sure if virtual inheritance is involved.

So, this is what I can do:

  1. Handle Derived->Base with static_cast<> in all cases
  2. Handle Base->Derived iff the classes are polymorphic, and use dynamic_cast for that case.

I think I’m OK with this compromise. I’m slightly nervous that I’ll get bug reports that are the opposite of your issue if I implement this. But I think I’m willing to risk it.


#7

Why that code needs to works ? in many languages you can’t call derived class function from pointer to base, not without some manual casting anyway.
If the script needs to call someFunction, I think it reasonable to expect the user to expose the right type like this

class Base { virtual ~Base() = default; };
class Derived : public Base { 
  public:
    void someFunction();
};

int main()
{
  chaiscript::ChaiScript chai;
  chai.add(chaiscript::base_class<Base, Derived>());
  chai.add(chaiscript::fun(&Derived::someFunction), "someFunction");
  auto d = std::make_shared<Derived>();
  std::shared_ptr<Base> b = d // Let's assume we need b for other uses
  chai.add(chaiscript::var(d), "d"); // d type is already the right type
  chai("d.someFunction()"); 
}

Though if the previous code is always valid in chaiscript, I can understand that you don’t want to break backward compability.


#8

It would be a breaking change to remove the functionality now.

But I believe I’ve intelligently implemented a fix that gets the example working, maintains existing functionality and uses the most efficient option when possible.

It’s on develop now.

-Jason