[C++-sig] Re: Interest in luabind

David Abrahams dave at boost-consulting.com
Tue Jun 24 22:42:04 CEST 2003


"Daniel Wallin" <dalwan01 at student.umu.se> writes:

>> >> I think that problem is a little more complicated than
>> >> you're making it out to be, and that your method ends
>> >> up being slower than it should be in inheritance
>> >> graphs of any size.  First of all, inheritance may be
>> >> a DAG and you have to prevent infinite loops if you're
>> >> actually going to support cross-casting.  Secondly
>> >> Boost.Python caches cast sequences so that given the
>> >> most-derived type of an object, you only have to
>> >> search once for a conversion to any other type, and
>> >> after that you can do a simple address calculation.
>> >> See libs/python/src/object/inheritance.cpp.  This
>> >> probably should be better commented; the algorithms
>> >> were hard to figure out and I didn't write down
>> >> rationale for them :( On the other hand, maybe being
>> >> fast isn't important in this part of the code, and the
>> >> cacheing should be eliminated ;-)
>> >
>> > We only support upcasting, so our method isn't that
>> > slow.
>>
>> Surely you want to be able to go in both directions,
>> though?  Surely not everyone using lua is interested in
>> just speed and not usability?
>
> We probably would like to be able to go in both
> directions.  We also don't want to force the user to
> compile with RTTI turned on

I'm sure that's a capability some Boost.Python users would
appreciate, too.

> so we currently supply a LUABIND_TYPEID macro to overload
> the typeid calls for a unique id for the type. 

This functions something like a specialization?

> This of course causes some problems if we want to
> downcast

And if you want to support CBD (component based development,
i.e. cross-module conversions), unless you can somehow get
all authors to agree on how to identify types.  Well, I
guess they could use strings and manually supply what
std::type_info::name() does.

> so we would need to be able to turn downcasting off, or
> let the user supply their own RTTI-system somehow.

That's pretty straightforward, fortunately.

We need to be careful about what kinds of reconfigurability
is available through macros.  Extensions linked to the same
shared library all share a link symbol space and thus are
subject to ODR problems.

>> >> > Ok, how do you handle conversions of lvalues from c++
>> >> > -> python?  The to_python converter associated with a
>> >> > UDT does rvalue conversion and creates a new object,
>> >> > correct?
>> >>
>> >> Yeah.  Implicit conversion of lvalues by itself with
>> >> no ownership management is dangerous so you have to
>> >> tell Boost.Python to do it.  I'm sure you know this,
>> >> though, since luabind supports "parameter policies."
>> >> Bad name, though: a primary reason for these is to
>> >> manage return values (which are not parameters).  So I
>> >> wonder what you're really asking?
>> >
>> > We convert lvalues to lua with no management by
>> > default. I don't think this is more dangerous than
>> > copying the objects, it's just seg faults instead of
>> > silent errors.
>>
>> <shiver>
>> Your way, mistakes by the user of the *interpreter* can
>> easily crash the system.  My way, only the guy/gal doing
>> the wrapping has to be careful:
>>
>>          >>> x = X()
>>          >>> z = x.y
>>          >>> del x
>>          >>> z.foo()  # crash
>>
>> The users of these interpreted environments have an
>> expectation that their interpreter won't *crash* just
>> because of the way they've used it.
>> </shiver>
>
> Right, I thought you always copied the object. My mistake.

OK, sorry for the heat.

>> > Both ways are equaly easy to make mistakes with.
>>
>> Totally disagree.  Done my way, we force the guy/gal to
>> consider whether he really wants to do something unsafe
>> before he does it.  You probably think I copy objects by
>> default, but I don't. That was BPLv1.  In BPLv2 I issue an
>> error unless the user supplies a call policy.
>>
>> Finally, let me point out that although we currently use
>> Python weak references to accomplish this I realized last
>> night that there's a *much* easier and more-efficient way
>> to do it using a special kind of smart pointer to refer to
>> the referenced object.
>
> Ah ok, function which returns lvalues causes compile time
> errors. 

Relatively pretty ones, too.  See
boost/python/default_call_policies.hpp

> When we decided to do it our way we thought returning
> unmanaged lvalue's would be the most common usage.  We
> only considered copying the object as an alternative,
> perhaps it's better to give compile time errors.

It's *miles* better.  Otherwise users will just blindly wrap
these things unsafely without considering the consequences.
Remember that the segfault may not occur in their tests, due
to (un)lucky usage patterns.

>> > Our policies primary reason is not to handle return
>> > values, but to handle conversion in both
>> > directions. For example, adopt() can be used to steal
>> > objects that are owned by the interpreter.
>> >
>> > void f(A*); def("f", &f, adopt(_1))
>>
>> What, you just leak a reference here?  Or is it something
>> else?  I had a major client who was sure he was going to
>> need to leak references, but he eventually discovered
>> that the provided call policies could always be made to
>> do something more-intelligent, so I never put the
>> reference-leaker in the library.  I haven't had a single
>> request for it since, either.
>
> The above is (almost) the equivalent of:
> void f(auto_ptr<A>*);

What's the significance of a pointer-to-auto_ptr?  I'd
understand what you meant if you wrote:

     void f(auto_ptr<A>);

instead.  I'm going to assume that's what you meant.

> It is very useful when wrapping interfaces which expects the
> user to create objects and give up ownership.

Sure, great.  It's a function-call-oriented thing.  Before
you object, read on.

>> Hmm, this is really very specific to calls, because it
>> *does* manage arguments and return values.  I really think
>> CallPolicy is better.  In any case I think we should
>> converge on this, one way or another; there will be more
>> languages, and you do want to be able to steal my users,
>> right? <wink>.  That'll be a lot easier if they see
>> familiar terminology ;-)
>
> I don't think it's specific to calls, but to all conversion
> of types between the languages. We can use policies when
> fetching values from lua, or when calling lua functions from
> c++:
>
> A* stolen_obj = object_cast<A*>(get_globals(L)["obj"],
> adopt(result));

What is result?  A placeholder?

could be spelled:

    std::auto_ptr<A> stolen
        = extract<std::auto_ptr<A> >(get_globals(L)["obj"]);

in Boost.Python.

[I assume this means that all of your C++ objects are held
within their lua wrappers by pointer.  I went to
considerable lengths to allow them to be held by-value,
though I'm not sure the efficiency gain is worth the cost
in flexibility.]

> call_function<void>(L, "f", stolen_obj) [ adopt(_1) ];

That's spelled:

  call_function<void>(L, "f", std::auto_ptr<A>(stolen));

I can begin to see the syntactic convenience of your way,
but I worry about the parameterizability.  In the first case
"result" is the only possible appropriate arg and in the 2nd
case it's "_1".

> And yeah, of course stealing your users is our goal. :)

I certainly hope so!  Likewise, I'm sure!

>> >> I'm not committed to the idea of a single registry.
>> >> In fact we've been discussing a hierarchical registry
>> >> system where converters are searched starting with a
>> >> local module registry and proceeding upward to the
>> >> package level and finally ending with a global
>> >> registry as a fallback.
>> >
>> > Right, that seems reasonable.
>>
>> Cool.  And let me also point out that if the module
>> doesn't have to collaborate with other modules, you don't
>> even need a registry lookup at static initialization
>> time.  A static data member of a class template is enough
>> to create an area of storage associated with a C++ type.
>> There's no central registry at all in that case.  I have
>> grave doubts about whether it's worth special-casing the
>> code for this, but it might make threading easier to cope
>> with.
>
> I can't see why it would be worth it. 

I like your attitude.

> If the module doesn't interact with other modules
> threading wouldn't be an issue?  So how could it make it
> easier?

I guess only in the case that the modules are loadedq
multiple times by different threads *and* the compiler
provides threadsafe static initializers, you wouldn't have
to worry about the central registry being modified while
someone was reading it.  A minor issue, really.  Mutexes
handle everything.

>> >> My big problem was trying to figure out a scheme for
>> >> assigning match quality.  C++ uses a kind of
>> >> "substitutaiblity" rule for resolving partial ordering
>> >> which seemed like a good way to handle things.  How do
>> >> you do it?
>> >
>> > We just let every converter return a value indicating
>> > how good the match was, where 0 is perfect match and -1
>> > is no match. When performing implicit conversions,
>> > every step in the conversions inreases the match value.
>> >
>> > Maybe I'm naive, but is there need for anything more
>> > complicated?
>>
>> Well, it's the "multimethod problem": consider base and
>> derived class formal arguments which both match an actual
>> argument, or int <--> float conversions.  How much do you
>> increase the match value by for a particular match?
>
> I don't know if I get this.

Are you familiar with the problems of multimethod
dispatching?  Google can help.

> We just increase the match value by one for every casting
> step that is needed for converting the types.

That seems to work for all the trivial cases, but the
problem is always phrased in more-complicated terms, I
presume for a reason.  See http://tinyurl.com/f5t6

One example of a place where it might not work is:

struct B {}; struct D : B {};

void f(B*, python::list)
void f(D*, std::vector<int>)

   >>> f(D(), [1, 2, 3])

I want this to choose the first overload, since it requires
only lvalue conversions.

Other links of interest:
  http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1463.html
  http://tinyurl.com/f5vi

None of these uses such a trivial algorithm for rating
matches.  Coincidence?

> Right. Also, you can't get a pointer to all objects in lua,
> only "userdata" objects. If the object being converted is of
> primitive type, you can only access it directly from the
> stack with lua_toXXX() calls.

OK

>> > It doesn't seem that interesting to register different
>> > conversions for different states anymore. (at least not
>> > to me, but I could be wrong..). But if we where to
>> > increase the isolation of the registries, each state
>> > could just as well get their own registry.
>>
>> Let's continue poking at the issues until we get clarity.
>
> Yeah, I'll have to think about this for a bit, the whole
> registry thing is quite new to me.

OK.  FYI I'm going on vacation 6/26-7/6.  I find this
conversation really interesting, though, so I'll try to
keep an eye on it.

-- 
Dave Abrahams
Boost Consulting
www.boost-consulting.com





More information about the Cplusplus-sig mailing list