RE: Implicit casting issues when binding to C++

  • From: "Janis Britals" <jbritals@xxxxxxxxx>
  • To: <luajit@xxxxxxxxxxxxx>
  • Date: Tue, 10 Jul 2012 11:15:32 +0200

Mike Pall wrote:

> One could add a special ctype metamethod (name to be 
> determined) that's called for a destination type when a 
> conversion would otherwise fail. This should cover implicit 
> C++ constructors D::D(S).

That would be great. Exactly what's needed.

> I'm not too fond of calling an extra metamethod for the 
> source type, which would cover C++ conversion functions 
> S::operator D() (but I'll need to do this for real C++ 
> support, of course). This causes ordering issues, as the 
> first metamethod to be called may need to signal that it has 
> no matching conversion. [In C++ this is statically resolved 
> and ambiguities are disallowed.]

Well, it's not that big of a loss. In my experience lion's share (probably
more than 95%) of all implicit casts in C++ code are of the constructor
kind. In all of Qt I've come across only a few of the casting operators, and
the ones that are really necessary (like operator QVariant()) you can still
implement using the implicit constructor casts (you can extend the implicit
QVariant constructor to cover these cases). You really only lose casts to
POD types, like operator char const *() or operator int(), but you may argue
even that you are actually better off without them.

However there's one more situation requiring "forward" type conversion (i.e.
based on the source type), and it is static_cast<BaseType*>. This is really
indispensable for C++. Luckily it does not need any extra calls into VM,
just some simple pointer arithmetic. Here maybe the "matrix" solution is not
such a bad idea after all. You basically need a list of all base ctypes with
the "offset" you need to add to "this" pointer to get them. Maybe you can
store them in one tidy table inside ctype metatype (like __base or
something). Please give it some thought, Mike.
 
> IMHO even the existence of __new itself isn't enough to 
> warrant an implicit constructor call. There's a reason C++ 
> has the (underused) 'explicit' keyword for constructors. If 
> you really want the implicit conversions and are prepared to 
> handle all possible input arguments, then you can make the 
> conversion metamethod the same as __new, of course.

Agree 100%. Qt actually uses the 'explicit' constructor specification quite
extensively, and I follow their lead in my bindings, i.e. I allow only those
implicit casts that are allowed in Qt. I think it would lead to ambiguities
in certain method overloading situations, if some of these casts were
allowed.

> I'm not sure whether a minimal solution (only resolve 
> destination, not JIT-compiled) would be acceptable or even 
> useful for most users.
> Opinions?

I can't vote fore most users, but it would certainly be a huge help to me
and the users of our application. It would help me to get rid of most or
maybe all of my hacks.

In order for me to be able to use 'vanilla' LuaJIT, there is just one small
additional requirement for this "implicit casting" metamethod: in case it is
invoked, it would be great if it received the original Lua TValue* as
argument, or if it is not easily possible, at least preserve the enum
specification for enum arguments. Currently enum types are stripped before
passing to 'lj_cconv_ct_ct' function. Unfortunately C++ enums are not quite
C enums. In C enum really is synonymous with int, it's just a convenience
for using constants in code. C++ regards enums as fully separate types, and
certain method overloads (including implicit constructors) depend on the
actual enum type of the passed argument. I even looked at the possibility of
representing C++ enums in FFI with C structs (using 'struct { int _; }'
wrapper) in order to preserve their type characteristics, and enable some
metamethods on them, but in the end decided against it since it introduced
too much runtime overhead.

I vote with both hands for your proposal!

--Janis


Other related posts: