emscripten-core / emscripten-core/emscripten

embind cannot call lambda taking a const reference via const raw pointer

Open
#2,825 5 comments 0 reactions 0 assignees View on GitHub
embind
Dominant language
C++
Stars
27.6k
Forks
3.6k
Avg merge
1d 1h
Merged PRs (30d)
105

Description

It does not seem possible to call a lambda taking a const reference, registered to a class with `class_::function`, via a const raw pointer. Although it compiles, it is disallowed at runtime. I am testing with Emscripten 1.22 on Windows.

I'm not sure if the first sentence of the previous paragraph made sense, so consider this example:

``` c++
#include
#include

using namespace emscripten;

class MyClass
{
public:
static MyClass* createInstance()
{
return new MyClass;
}

static const MyClass* createConstInstance()
{
return new MyClass;
}

void doWork()
{
std::cerr << "Doing work in MyClass instance" << std::endl;
}

void doWorkConst() const
{
std::cerr << "Doing const work in MyClass instance" << std::endl;
}
};

EMSCRIPTEN_BINDINGS(MyBindings)
{
class_("MyClass")
.class_function("createInstance", &MyClass::createInstance, allow_raw_pointers())
.class_function("createConstInstance", &MyClass::createConstInstance, allow_raw_pointers())
.function("doWork", &MyClass::doWork)
.function("doWorkConst", &MyClass::doWorkConst)
.function("doWorkWrapper",
select_overload(
[] (MyClass& self)
{
std::cerr << "Doing some work in the wrapper" << std::endl;
self.doWork();
std::cerr << "Doing some more work in the wrapper" << std::endl;
}))
.function("doWorkConstWrapper",
select_overload(
[] (const MyClass& self)
{
std::cerr << "Doing some const work in the wrapper" << std::endl;
self.doWorkConst();
std::cerr << "Doing some more const work in the wrapper" << std::endl;
}))
;
}
```

Now, if you create a non-const instance with

``` javascript
var instance = Module.MyClass.createInstance();
```

then all of the methods-- `doWork`, `doWorkConst`, `doWorkWrapper`, and `doWorkConstWrapper`-- are callable.

However, if you create a const instance with

``` javascript
var instance = Module.MyClass.createConstInstance();
```

then the results are not as expected:

```
> instance.doWork()
BindingError: Cannot convert argument of type MyClass const* to parameter type MyClass*
> instance.doWorkWrapper()
BindingError: Cannot convert argument of type MyClass const* to parameter type MyClass
> instance.doWorkConst()
Doing const work in MyClass instance test1.html:1245
> instance.doWorkConstWrapper()
BindingError: Cannot convert argument of type MyClass const* to parameter type MyClass
```

`doWork` and `doWorkWrapper` are rightly not callable, although the error message for the latter is slightly different from the former. This is revelatory when considering `doWorkConstWrapper`, which should work, but fails with the same message as `doWorkWrapper`.

I've dug into this somewhat, and it seems to come down to the fact that `TypeID` and `TypeID` return the same value. While the first two forms of `class_::function`, which deal with member functions and const member functions, explicitly call `AllowedRawPointer` and `AllowedRawPointer`, respectively, when forming the ArgTypeList, the third form which deals with regular functions (and therefore lambdas), uses ThisType directly in the ArgTypeList. `AllowedRawPointer` and `AllowedRawPointer` DO return a different TypeID, so the distinction works correctly when dealing with member functions.

I did try to fix this by modifying the third form of `class_::function` to replace:

``` c++
typename WithPolicies::template ArgTypeList args;
```

with:

``` c++
typename WithPolicies::template ArgTypeList::type>, Args...> args;
```

This actually appears to work in my limited test case, however it breaks smart pointer support, and I'm not sure where to go from there. I'm not familiar enough with the internals of embind to go much further.

As an aside, I realize that using raw pointers is not the recommended workflow for embind. In my actual use case however, the object lifetime is managed by another object within the C++ code, and neither shared nor unique ownership by the embind layer is correct. Therefore, raw pointers seemed to be the way to go.

Any input on this is appreciated!

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.