std::unique_ptr<T> experiments


#1

I have tried the std::unique_ptr<T> support in the last few weeks. I have to register user-defined type conversions in order to call methods on the wrapped pointer. Basically, the type std::unique_ptr<T> needs to be converted to either T or T*. Also, when calling methods from a base class, more user-defined type conversions are required. Assuming the type D derived from base class B, std::unique_ptr<D> requires a conversion to either B or B* for those methods to be called.
There is one problem which I couldn’t work around with user-defined type conversions though. It is the conversion from std::unique_ptr<D> to std::unique_ptr<B>&&. I couldn’t find a way to make it possible to call test(…) and this is the last use case that is giving me troubles to transfer ownership from chaiscript to C++. Here is some sample code to illustrate this.

class B
{
public:
	std::function<int()> f(int c)
	{
		return [c]() {
			return c;
		};
	}
	
	void g(std::function<int()> f)
	{
		std::cout << f() << '\n';
	}
};

class D : public B
{
public:
	void h()
	{
		std::cout << "h()\n";
	}
};

std::unique_ptr<D> makeD()
{
	return std::make_unique<D>();
}

void test(std::unique_ptr<B>&& b)
{
	std::unique_ptr<B> consume = std::move(b);
	consume->g(consume->f(3));
}

int main(int argc, const char * argv[])
{
	chaiscript::ChaiScript c;
	chaiscript::ModulePtr m = chaiscript::ModulePtr(new chaiscript::Module());
	chaiscript::utility::add_class<B>(*m, "B", {}, {
		{chaiscript::fun(&B::f), "f"},
		{chaiscript::fun(&B::g), "g"}
	});
	chaiscript::utility::add_class<D>(*m, "D", {}, {
		{chaiscript::fun(&D::h), "h"}
	});
	m->add(chaiscript::base_class<B, D>());
	m->add(chaiscript::fun(makeD), "makeD");
	m->add(chaiscript::fun(test), "test");
	m->add(chaiscript::type_conversion<std::unique_ptr<D>, D*>([](const std::unique_ptr<D>& uptr) {
		return uptr.get();
	}));
	m->add(chaiscript::type_conversion<std::unique_ptr<D>, B*>([](const std::unique_ptr<D>& uptr) {
		return dynamic_cast<B*>(uptr.get());
	}));
	c.add(m);
	c.eval("var d = makeD();"
		 "d.h();"
		 "d.g(d.f(5));"
		 "test(d);");
	return 0;
}

And here is the output I get :

h()
5
libc++abi.dylib: terminating with uncaught exception of type chaiscript::exception::eval_error: Error: "Error calling function 'test'" With parameters: (NSt3__110unique_ptrI1DNS_14default_deleteIS1_EEEE) 

Any suggestions on how to support calling the test(…) method with the result from makeD(…)?


#2

I’d like to start with the first part, your comment that you have to provide your own versions between std::unique_ptr<D> and D*. This is one thing I’ve made a point of testing in the latest develop branch work, see here:

Can you verify what version of ChaiScript you are starting from and maybe provide a simple example that fails that most basic unique_ptr to * test?

Thank you,
Jason


#3

To answer your question, I am using the head of the master branch from github. The sample code I have provided in my original post reproduces the problems I am describing.

When commenting out the type_conversion from std::unique_ptr<D> to D* included in that sample, I get the following exception :

libc++abi.dylib: terminating with uncaught exception of type chaiscript::exception::eval_error: Error: “Error with function dispatch for function ‘h’” With parameters: (NSt3__110unique_ptrI1DNS_14default_deleteIS1_EEEE).()

Otherwise, when the type_conversion is added to the module, then I get the following output instead :

h()
5
libc++abi.dylib: terminating with uncaught exception of type chaiscript::exception::eval_error: Error: “Error calling function ‘test’” With parameters: (NSt3__110unique_ptrI1DNS_14default_deleteIS1_EEEE)

Also I went in and added a new unit test to call methods through unique_ptr and reproduced this problem. I used the class A which is defined right after the “Use unique_ptr” test case. I have added a single method to class A which you will find below. Here is the new failing test case :

class A
{
  public:
    A() = default;
    A(const A&) = default;
    A(A &&) = default;
    A &operator=(const A&) = default;
    A &operator=(A&&) = default;
    virtual ~A() = default;

    int getI() const {return 5;}
};

TEST_CASE("Call methods through unique_ptr")
{
    chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(),create_chaiscript_parser());

    chai.add(chaiscript::var(std::make_unique<A>()), "uptr");
    CHECK(chai.eval<int>("uptr.getI()") == 5);
}

#4

The std::unique_ptr support is not yet on master, it’s only on develop branch.


#5

Okay, sorry for the confusion, I just went back to github and realized I did get the develop branch as it was the one selected by default on the landing page for the ChaiScript project. It does include this commit : https://github.com/ChaiScript/ChaiScript/commit/fb7f8f194cc85daec881605e62bef85607a2ff66 on which I commented in early december 2016.


#6

Ok, I’ll start with your unit test and see what I find. Then I’ll probably release Chai 6.0 after that. This release has been sitting for far too long.


#7

Ok, I’ve taken your test case modified it (slightly, to fix a bug and make the names unique) and it executes with no problem at all on the develop branch.

class Unique_Ptr_Test_Class
{
  public:
    Unique_Ptr_Test_Class() = default;
    Unique_Ptr_Test_Class(const Unique_Ptr_Test_Class&) = default;
    Unique_Ptr_Test_Class(Unique_Ptr_Test_Class &&) = default;
    Unique_Ptr_Test_Class &operator=(const Unique_Ptr_Test_Class&) = default;
    Unique_Ptr_Test_Class &operator=(Unique_Ptr_Test_Class&&) = default;
    virtual ~Unique_Ptr_Test_Class() = default;

    int getI() const {return 5;}
};

TEST_CASE("Call methods through unique_ptr")
{
    chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(),create_chaiscript_parser());

    chai.add(chaiscript::var(std::make_unique<Unique_Ptr_Test_Class>()), "uptr");
    chai.add(chaiscript::fun(&Unique_Ptr_Test_Class::getI), "getI");
    CHECK(chai.eval<int>("uptr.getI()") == 5);
}

Are you certain you are running with the code from develop and your chaiscript build is up to date?

-Jason


#8

Hi Jason,

I am in fact using branch develop, I verified it with the following results :

dalzhim$ git status
On branch develop
Your branch is up-to-date with 'origin/develop'.

I pulled your last commit and the test passed successfully. I realize now that the test case I submitted failed for the wrong reasons. So I worked a bit more in order to reproduce the problem and here’s an updated version of the test case that fails over here. The Unique_Ptr_Test_Class is left unchanged.

std::unique_ptr<Unique_Ptr_Test_Class> make_Unique_Ptr_Test_Class()
{       
        return std::make_unique<Unique_Ptr_Test_Class>();
}

TEST_CASE("Call methods through unique_ptr")
{
    chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(),create_chaiscript_parser());

    chai.add(chaiscript::fun(make_Unique_Ptr_Test_Class), "make_Unique_Ptr_Test_Class");
    chai.add(chaiscript::fun(&Unique_Ptr_Test_Class::getI), "getI");
    CHECK(chai.eval<int>("var uptr = make_Unique_Ptr_Test_Class();uptr.getI()") == 5);
}

#9

I added your test and fixed the issue here: https://github.com/ChaiScript/ChaiScript/commit/1cb15d8b226818464495399d57ac95edc8bb3012

-Jason


#10

Great! Now calling a method on type T through std::unique_ptr<T> works great. I have submitted a pull request for a new failing test case where I try to call a method on type Base through std::unique_ptr<Derived>.


#11

With our last iterations, the first three statements are now working great without the need for manual type conversion registration.

c.eval(
  "var d = makeD();"
  "d.h();"
  "d.g(d.f(5));"
  "test(d);"
);

Only the test function call problem remains and I’ve submitted a new pull request with a test case reproducing it.