Multiple inheritance

39 views
Skip to first unread message

Mike Lischke

unread,
Aug 14, 2023, 5:16:29 AM8/14/23
to emscripte...@googlegroups.com
Hi all,

C++ multiple inheritance is not discussed in the documentation and there are only very old issues in the emscripten issue tracker. From what I read so far is that multiple inheritance is not supported in embind because JS does not support that. That's not false, but MI is used so often in C++ there must be a workaround (and I'm looking for one).

In my case I have `class Lexer : public Recognizer, public TokenSource {}`. In some places I need that lexer as a recognizer and in some I need it as token source. My bindings are like this (simplified):

class_<Lexer, base<Recognizer>>("Lexer$Internal")
.property("mode", &Lexer::mode);

class_<LexerHelper, base<Lexer>>("Lexer")
.constructor<CharStream *>()
.allow_subclass<LexerWrapper>("LexerWrapper", constructor<CharStream *>());

Since I cannot specify both classes as base in the bindings call, I started with `base<Recognizer>` which works fine. However, when I try to use the lexer class in a place where a token source is required, I get the error:

BindingError: Expected null or instance of TokenSource, got an instance of Recognizer at throwBindingError (file:...)

Side note: that approach to use `Lexer$Internal` and `LexerHelper` comes from the fact that `Lexer` is an abstract class, but I need to bind its constructor to allow calling that from JS where I use the Lexer to derive a JS class from. So, I created the (non-abstract) `LexerHelper` class and pretend it's the actual lexer class.

IIUC the error is bogus, because the checked class indeed derives from both base classes, only JS doesn't know that. What can I do to make this scenario work?

I experimented with leaving out the `Lexer` binding entirely (to avoid the MI dilemma), but that only caused the generated JS class to be not extendable.

Mike Lischke

unread,
Aug 14, 2023, 5:40:55 AM8/14/23
to emscripten-discuss
One possible workaround is to use the derived class in the bindings, where one of its base classes is required. That even works without changing the underlying C++ library code. In my case I changed:

class_<CommonTokenStream, base<BufferedTokenStream>>("CommonTokenStream")
.constructor<TokenSource *>()
.constructor<TokenSource *, size_t>();

to:

class_<CommonTokenStream, base<BufferedTokenStream>>("CommonTokenStream")
.constructor<Lexer *>()
.constructor<Lexer *, size_t>();

That limits the acceptable token source to lexers only, but in my case this is acceptable. Any other suggestion is still welcome!

Sam Clegg

unread,
Aug 14, 2023, 1:24:50 PM8/14/23
to emscripte...@googlegroups.com
On Mon, Aug 14, 2023 at 2:40 AM 'Mike Lischke' via emscripten-discuss <emscripte...@googlegroups.com> wrote:
One possible workaround is to use the derived class in the bindings, where one of its base classes is required. That even works without changing the underlying C++ library code. In my case I changed:

class_<CommonTokenStream, base<BufferedTokenStream>>("CommonTokenStream")
.constructor<TokenSource *>()
.constructor<TokenSource *, size_t>();

to:

class_<CommonTokenStream, base<BufferedTokenStream>>("CommonTokenStream")
.constructor<Lexer *>()
.constructor<Lexer *, size_t>();

That limits the acceptable token source to lexers only, but in my case this is acceptable. Any other suggestion is still welcome!

This workaround makes sense to me.  

I imagine it would be very rare to expose the full details/complexity of the type hierarchy to JS.  Exposing a simplified hierarchy to JS is probably the way to go.

cheers,
sam


On Monday, August 14, 2023 at 11:16:29 AM UTC+2 Mike Lischke wrote:
Hi all,

C++ multiple inheritance is not discussed in the documentation and there are only very old issues in the emscripten issue tracker. From what I read so far is that multiple inheritance is not supported in embind because JS does not support that. That's not false, but MI is used so often in C++ there must be a workaround (and I'm looking for one).

In my case I have `class Lexer : public Recognizer, public TokenSource {}`. In some places I need that lexer as a recognizer and in some I need it as token source. My bindings are like this (simplified):

class_<Lexer, base<Recognizer>>("Lexer$Internal")
.property("mode", &Lexer::mode);

class_<LexerHelper, base<Lexer>>("Lexer")
.constructor<CharStream *>()
.allow_subclass<LexerWrapper>("LexerWrapper", constructor<CharStream *>());

Since I cannot specify both classes as base in the bindings call, I started with `base<Recognizer>` which works fine. However, when I try to use the lexer class in a place where a token source is required, I get the error:

BindingError: Expected null or instance of TokenSource, got an instance of Recognizer at throwBindingError (file:...)

Side note: that approach to use `Lexer$Internal` and `LexerHelper` comes from the fact that `Lexer` is an abstract class, but I need to bind its constructor to allow calling that from JS where I use the Lexer to derive a JS class from. So, I created the (non-abstract) `LexerHelper` class and pretend it's the actual lexer class.

IIUC the error is bogus, because the checked class indeed derives from both base classes, only JS doesn't know that. What can I do to make this scenario work?

I experimented with leaving out the `Lexer` binding entirely (to avoid the MI dilemma), but that only caused the generated JS class to be not extendable.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/emscripten-discuss/819aacbf-ea39-46c6-bf8c-a4989cbc3760n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages