On 20 Sep 2011 at 12:38, Jim Bosch wrote: > I'd also considered having a different set of template conversions that > are checked first for performance reasons, but I'd actually viewed the > override preference argument from the opposite direction - once a > template converter traits class has been fully specialized, you can't > specialize it again differently in another module (well, maybe symbol > visibility labels can get you out of that bind in practice). So it > seemed a registry-based override would be the only way to override a > template-based conversion, and hence the registry-based conversions > would have to go first.
Ah, sorry, I didn't explain myself well at all. I've been doing a lot of work surrounding ISO C and C++ standards recently, so my head is kinda trapped in future C and C++. When I was speaking of ODR, I was kinda assuming that we have C++ modules available for newer compilers in the post C++-1x TR (see http://www.open- std.org/jtc1/sc22/wg21/docs/papers/2007/n2316.pdf) and we can emulate much of module support using -fvisibility=hidden on GCC. On MSVC, of course, you get module proto-support for free anyway due to how their DLLs work. You're absolutely correct that right now, outside of the Windows platform, ODR is a process wide problem in most compilers on their default settings. That's a PITA, so everyone is agreed that we ought to do something about it. The big problem is how far we ought to go, hence N2316 not making it into C++-1x and being pushed into TR. What I can say is that that TR will very likely be highly compatible with the Windows DLL system (and its GCC visibility near-equivalent) due to backwards compatibility. I would suggest that you code as if both are true as a reasonable proxy for future C++ module support. Then you're covered ten years down the line from now. > But overall I think your proposal to just try the templates first is > cleaner, because having multiple specializations of the same traits > class in different modules would be a problem either way; allowing users > to override the compile-time conversions with registry-based conversions > is at best a poor workaround. I know this is a little off-topic, but Boost could really do with a generic runtime type registry implementation. There are lots of use cases outside BPL and if we had one, highly extensible, properly written system it could be applied to lots of use cases. For example, Java-style automagical metaprogrammed C++ type reflection into SQL is perfectly possible. At the time I wrote it, it was the only example of it anywhere I could find (maybe things have since changed). It makes talking to databases super-easy at the cost of making the compiler work very hard. There are lots more use cases too e.g. talking with .NET, or Objective C. > > Just make sure what you do works with precompiled headers :) > > > > P.S.: This is trickier than it sounds. > > Yuck. Precompiled headers are something I've never dealt with before, > but I suppose I had better learn. Getting them working can make the difference between a several hour recompile and ten minutes. They're painful though due to compiler bugs. > > The same mechanism usefully also takes care of multiple python > > interpreters too. > > I have to admit I'm only barely following you here - threads are another > thing I don't deal with often. It sounds like you have a totally > different option from the ones I was anticipating. Could you explain in > more detail how this would work? Sure. You have the problem when working with Python of handling the GIL which is strongly related to what the "current" interpreter is. These are TLS items in python, so each thread has its own current setting. Therefore, what one really ought to have in BPL is something like: // normal C++ code ... // I want to call python code in interpreter X { boost::python::hold_interpreter interpreter_holder(X); // Replaces "current" interpreter with X boost::python::hold_GIL gil_holder(interpreter_holder); // Acquire the GIL for that interpreter call_some_BPL_or_python_function(); } // On scope exit gil_holder and interpreter_holder gets destroyed, thus releasing the GIL and resetting the "current" interpreter to whatever it was before // Back to normal C++ code This obviously refers to the embedded case, but it ought to be similar when BPL calls into C++: the "current" interpreter should be available per thread as a BPL object instance wrapping the python TLS config. Then a call into C++ can safely call into other interpreters. What's useful here of course is that you can keep a per-thread list of interpreter nestings. This means you can see exactly which module entered which interpreter and in which order, and therefore what to search and what to unwind when necessary. > An interesting idea - avoid trying all possible conversions a runtime > seems a very worthy goal, though I could also see this inflating the > size of the modules. Can you point me at anything existing for an example? The closest that I have publicly available is the SQL type reflection machinery in TnFOX. Have a look at the following: https://github.com/ned14/tnfox/blob/master/include/TnFXSQLDB.h https://github.com/ned14/tnfox/blob/master/include/TnFXSQLDB_ipc.h https://github.com/ned14/tnfox/blob/master/include/TnFXSQLDB_sqlite3.h Note that this is an entirely *static* type registry, so it exists exclusively in the compiler. It does happily extend into a dynamic registry however. I can supply the source which extends the TnFOX static registry with a dynamic runtime, but I'd need you to agree to an NDA and a promise not to distribute them. > If I understand your argument, it's not the global registry that causes > ODR violations - it's the fact that you're trying to mimic having local > registries by forcing distinct BPLs for each module, and that makes BPL > symbols ambiguous. If you had a pair of modules that were happy using > each other's converters, they would do the standard thing and share one > BPL and one registry and you wouldn't have any ODR problems. ODR is a C++ (and C) spec issue and has nothing to do with BPL per se. It's rather that because real world code routinely violates ODR that it becomes a problem for anything which operates a type registry. BTW code can't help violating it. Libraries have absolutely no control over what they must coexist with in a given process. > In other words, it's not the fact that DLL D and DLL E register > different conversions for class foo that causes the ODR problems; that > just makes modules interact unfortunately (but in a deterministic and > debuggable way). It's the workaround (loading multiple BPLs) that > causes the actual ODR problems. RTLD_GLOBAL operates okay for most C++ programs because that's the default. Indeed, until very recently, GCC couldn't throw exceptions properly unless RTLD_GLOBAL was set. Unfortunately, Python sets RTLD_LOCAL for the process because up until I patched GCC to add -fvisibility, there was no easy way to separate Python extension modules from one another. They routinely defined functions with identical symbols and therefore one got all sorts of unpleasant conflicts. One therefore gets a big problem when using anything C++ with a type registry within Python. One typically has to resort to unpleasant hacking of dlopen settings. > So it sounds we agree that we should only ever have one BPL loaded. We > just need to implement the registry so it can know which module DLL > instance a particular registry lookup is coming from, whether that's > using special module-instance IDs or compiling the registries into the > module DLLs or something else. > > Is that right? Ah, but it gets worse! You can't guarantee that BPL won't be loaded multiply anyway. For example, one might have dependencies on two separate versions of BPL, or some sublibrary might link a copy of BPL in statically. In fact, you can't even guarantee that there aren't multiple pythons running! One (nasty) way of implementing parallel python is to instantiate multiple pythons each with their own GIL and run them in separate threads. Of course, forking yourself is far saner. In the end though, BPL is a *library*. You have absolutely no control over what you're combined with, but you can try your best for most reasonable scenarios. I know this sounds tricky, but what you need is a design which copes with having one BPL loaded or many and/or one python loaded or many and/or one interpreter running or many. If you follow the system described above where each thread keeps a list of which BPL and python interpreter is "current", you now know which type registries to search in any given scenario. You can see most of an existing implementation of what I described above at: https://github.com/ned14/tnfox/blob/master/Python/FXPython.h https://github.com/ned14/tnfox/blob/master/Python/FXPython.cxx And oh, BTW, here is a very useful piece of C++ metaprogramming for BPL: https://github.com/ned14/tnfox/blob/master/Python/FXCodeToPythonCode.h This lets you handle a limitation in present BPL where you want to supply one of a list of python functions as a C callback function e.g. a comparison function for sorting. The metaprogramming generates a N member jump table and you supply a policy which thunks the C callback into Python. You can then install or deinstall python functions to the "slot" as it were and pass the appropriate C wrapper to the C callback function taking code. In other words, the metaprogramming generates a unique C function address for each unique Python function (for the possibilities supplied). This is extremely useful. Hope these help. If you have any questions, please do ask. I always felt it a shame I never had the time to port TnFOX extensions to BPL back into Boost, kinda wasted me writing it all as no one uses TnFOX :( Niall -- Technology & Consulting Services - ned Productions Limited. http://www.nedproductions.biz/. VAT reg: IE 9708311Q. Company no: 472909. _______________________________________________ Cplusplus-sig mailing list Cplusplus-sig@python.org http://mail.python.org/mailman/listinfo/cplusplus-sig