Hi Colomban,
Whilst not Geany specific, we were discussing it on #geany and it
shows a use-case for the C++ pointer to member syntax.
Consider interfaces between separately compiled things, like Geany
plugins. (I am not proposing changing the Geany plugin interface, just
using it as an example).
C interface
struct a{
int b;
};
void a_f( struct a* );
C++ interface
struct a{
int b;
void f();
};
The requirement is to minimise the need to re-compile the plugin so
old plugins will work for as long as possible. In the following "pass
to the plugin" may be as parameters and/or table/struct entries, but
not as resolvable symbols.
Simplest approach C:
In Geany have a c; a* p = &c; pass p to the plugin
In the plugin do p->b and a_f( p ), but a_f needs to be resolved by
dlopen when the plugin is loaded (and there are a lot of functions in
the Geany interface) and the *definition* of a needs to be visible to
define p->b. So the plugin needs recompiling every time a changes,
although with C if you only add to the end it will remain binary
compatible so long as no compiler options or anything else change the
arrangement of the struct (eg packing it).
Simplest approach C++:
in Geany have a c; p = &c; pass p to plugin
In the plugin do p->b and p->f(), now dlopen doesn't need to resolve
the function names, but the dependence on the definition of a is still
there.
Single pointer approach C:
In Geany have a c; int *bp = &c.b; ahhh, there is no way to make a
pointer to a_f(b), ie to include the parameter, so this method applies
to data members only, pass bp to the plugin
In the plugin use *bp to access the data, fine, but we still must do
functions differently.
Single pointer approach C++:
In Geany have a c; int* bp = &c.b; ahhh, there is no way to make a
pointer to b.f() so this method applies to data members only, pass bp
to the plugin.
In the plugin use *bp to access the data, fine, but have to do
functions differently.
Two part (two pieces of information passed) approach in C:
In Geany have:
a c; size_t bo = offsetof(a, b); a* cp = &c; void (*fp)() = a_f; pass
bo, cp and fp to the plugin
In the plugin have *((int*)((char*)cp+bo)) to access the data and
(*fp)(cp) to call the function. The plugin no longer needs to know the
layout of a so it never needs to be re-compiled unless the types of
the members or functions it is using change, but data access is messy
and type unsafe and the approach differs between data and functions.
And I think it doesn't violate strict aliasing since the int* is a
pointer to a type which is a member of a so it can alias safely.
C like two part[1] approach in C++:
In Geany have:
a c; size_t bo = offsetof(a,b), ahhh no, can't guarantee the use of
offsetof on a non-POD type, and although a as it stands is ok, add a
constructor or destructor or private member or virtual member and
boom. So can't use offsetof on data.
Still can't make a pointer (as an ordinary pointer) or an offset to a
member function, so this method fails for both data and functions.
C++ type two part approach in C++:
In Geany have:
a c; int a::*bp = &a::b; a* cp = &c; void (a::*fp)() = &a::f; pass bp,
cp, fp to the plugin
In the plugin use cp->*bp to access the data and (cp->*fp)() to call
the function, note that the plugin doesn't need to know *anything*
about a except its name, ie the "class a;" forward declaration is all
it needs. So the plugin never has to be recompiled unless the type of
the members or functions it is using change. Works for all types of
class a with virtual, private, multiple bases etc.
Colomban, thanks for your pushing me to get my head around this :)
Cheers
Lex
[1] in C++ you can of course fake passing two pieces of information as
one, using classes with overloaded operators that make it look like a
single pointer, this is left as an exercise for the reader.