Discussion:
Need some clever C++ idea
(too old to reply)
Chris Shearer Cooper
2010-09-28 13:54:52 UTC
Permalink
I've inherited some legacy code that I'm trying to fix up, basically
what it is doing is creating a table of items and accessor functions
like this:

typedef void (CWnd::*VF)(void);

struct X
{
char name[32];
int type;
VF getFunc;
VF setFunc;
};

X table[] =
{
{ "Name", TYPE_STR, (VF)&Y::GetName, (VF)&Y::SetName },
{ "Value", TYPE_INT, (VF)&Y::GetValue, (VF)&Y::SetValue },
{ "Length", TYPE_DOUBLE, (VF)&Y::GetLength, (VF)&Y::SetLength }
};

Where of course the functions look like this:
CString Y::GetName(void);
int Y::GetValue(void);
double Y::GetLength(void);

So basically there is no type-safety going on, if someone unknowingly
changes the signature of GetValue(), the code happily compiles and
then crashes in bizarre ways.

What I'm trying to do, is think of a way to insert some type-safety
into this system with a couple of restrictions:
1) can't rewrite the entire table initialization section (change must
be able to be implemented incrementally)
2) can't add a lot of additional data members to the struct X (tables
are huge already)
3) can't call a class constructor for each line in the table (table
should be generated at compile-time, not run-time)

I tried putting a variety of function signatures into a union, but
then I can't find any way to initialize a union as part of a struct
inside an array.

What I would like is something like a macro that I can insert, one
line at a time, into the table, so I get something like this:

X table[] =
{
STR("Name", &Y::GetName, &Y::SetName ),
{ "Value", TYPE_INT, (VF)&Y::GetValue, (VF)&Y::SetValue },
{ "Length", TYPE_DOUBLE, (VF)&Y::GetLength, (VF)&Y::SetLength }
};

and somehow this STR() macro enforces the correct parameters and
return type on GetName() and SetName().

Can I somehow use a type_cast<> to first ensure the function has the
right signature, and then blast it into the generic VF for insertion
into the struct?

Thanks,
Chris
David Webber
2010-09-29 08:12:37 UTC
Permalink
Post by Chris Shearer Cooper
I've inherited some legacy code that I'm trying to fix up, basically
what it is doing is creating a table of items and accessor functions
typedef void (CWnd::*VF)(void);
struct X
{
char name[32];
int type;
VF getFunc;
VF setFunc;
};
X table[] =
{
{ "Name", TYPE_STR, (VF)&Y::GetName, (VF)&Y::SetName },
{ "Value", TYPE_INT, (VF)&Y::GetValue, (VF)&Y::SetValue },
{ "Length", TYPE_DOUBLE, (VF)&Y::GetLength, (VF)&Y::SetLength }
};
CString Y::GetName(void);
int Y::GetValue(void);
double Y::GetLength(void);
So basically there is no type-safety going on, if someone unknowingly
changes the signature of GetValue(), the code happily compiles and
then crashes in bizarre ways.
What I'm trying to do, is think of a way to insert some type-safety
The bottom line then is surely to get rid of the (VF) casts. As soon as you
have explicit casts like that, you have no type safety.

Dave
--
David Webber
Mozart Music Software
http://www.mozart.co.uk
For discussion and support see
http://www.mozart.co.uk/mozartists/mailinglist.htm
Chris Shearer Cooper
2010-09-29 13:20:51 UTC
Permalink
Post by Chris Shearer Cooper
I've inherited some legacy code that I'm trying to fix up, basically
what it is doing is creating a table of items and accessor functions
typedef void (CWnd::*VF)(void);
struct X
{
  char name[32];
  int type;
  VF getFunc;
  VF setFunc;
};
X table[] =
{
  { "Name", TYPE_STR, (VF)&Y::GetName, (VF)&Y::SetName },
  { "Value", TYPE_INT, (VF)&Y::GetValue, (VF)&Y::SetValue },
  { "Length", TYPE_DOUBLE, (VF)&Y::GetLength, (VF)&Y::SetLength }
};
CString Y::GetName(void);
int Y::GetValue(void);
double Y::GetLength(void);
So basically there is no type-safety going on, if someone unknowingly
changes the signature of GetValue(), the code happily compiles and
then crashes in bizarre ways.
What I'm trying to do, is think of a way to insert some type-safety
The bottom line then is surely to get rid of the (VF) casts.  As soon as you
have explicit casts like that, you have no type safety.
Dave
--
David Webber
Mozart Music Softwarehttp://www.mozart.co.uk
For discussion and support seehttp://www.mozart.co.uk/mozartists/mailinglist.htm
Um ... yes ... the question is _how_.
David Webber
2010-09-29 15:38:54 UTC
Permalink
Post by Chris Shearer Cooper
Um ... yes ... the question is _how_.
Well apart from the fact that you've defined VF as a pointer type to a CWnd
member, and you're using it to cast a pointer to a member of an unspecified
class Y, you could just remove the (VF) casts.

I'm assuming Y is CWnd ????? If not, then the cast looks very risky to
me.

Dave
--
David Webber
Mozart Music Software
http://www.mozart.co.uk
For discussion and support see
http://www.mozart.co.uk/mozartists/mailinglist.htm
David Webber
2010-09-29 15:56:05 UTC
Permalink
Post by Chris Shearer Cooper
Um ... yes ... the question is _how_.
Well if you're going to have an array, it contains identical objects. If
those objects need to contain pointers to different kinds of function, then
you're going to have to cast things. And if you're going to have to cast
things, then you haven't got type-safety. So with your constraints, you'll
probably have to do it completely differently: the whole current approach
works by sacrificing type safety (if it does work).

It's all pretty horrible as it stands: Mr Legacy obviously like natty
shortcuts :-) For example, different member function pointers can be
different sizes (when virtual inheritance is an issue), and if that happens
I suspect you'll have deep problems.

I don't think there can be an easy trick, given the constraints you have.

Dave
--
David Webber
Mozart Music Software
http://www.mozart.co.uk
For discussion and support see
http://www.mozart.co.uk/mozartists/mailinglist.htm
Ulrich Eckhardt
2010-10-01 09:30:25 UTC
Permalink
Post by Chris Shearer Cooper
typedef void (CWnd::*VF)(void);
struct X
{
char name[32];
int type;
VF getFunc;
VF setFunc;
};
X table[] =
{
{ "Name", TYPE_STR, (VF)&Y::GetName, (VF)&Y::SetName },
{ "Value", TYPE_INT, (VF)&Y::GetValue, (VF)&Y::SetValue },
{ "Length", TYPE_DOUBLE, (VF)&Y::GetLength, (VF)&Y::SetLength }
};
[...]
Post by Chris Shearer Cooper
So basically there is no type-safety going on,
[...]
Post by Chris Shearer Cooper
What I'm trying to do, is think of a way to insert some type-safety
1) can't rewrite the entire table initialization section (change must
be able to be implemented incrementally)
2) can't add a lot of additional data members to the struct X (tables
are huge already)
3) can't call a class constructor for each line in the table (table
should be generated at compile-time, not run-time)
[..]
Post by Chris Shearer Cooper
What I would like is something like a macro that I can insert, one
X table[] =
{
STR("Name", &Y::GetName, &Y::SetName ),
{ "Value", TYPE_INT, (VF)&Y::GetValue, (VF)&Y::SetValue },
{ "Length", TYPE_DOUBLE, (VF)&Y::GetLength, (VF)&Y::SetLength }
};
and somehow this STR() macro enforces the correct parameters and
return type on GetName() and SetName().
Macros are used for token-pasting, they are completely ignorant of types, so
that approach won't work. What you could do is this:

HACK("Name", TYPE_STR, &Y::GetName, &Y::SetName),

which resolves to the former line in a non-diagnostic build. For a
diagnostic build, struct X gets an additional last member "bool invalid;".
By default, it is initialised to 0 by the old lines, which is okay. For
those with the hack, it is initialised to a value you determine from the
types of the two functions and the given type, e.g. like this:

// declaration only
template<typename ClassType, typename ValueType>
int
signature(ValueType (ClassType::*)(), void (ClassType::*)(ValueType));

// specialisation for Y/CString
template<>
int
signature<Y, CString>(CString (Y::*)(), void (Y::*)(CString))
{
return TYPE_STR;
}

Then, once on startup you check if any of the table's elements have
their "invalid" flag set.

Yes, this means it is generated at runtime in a special diagnostic build,
though you might be able to fix this doing some more template
metaprogramming. You could e.g. make the initialiser for the second field

#define HACK(_1, _2, _3, _4)\
{_1, (check<_2,_3,_4>::value, _2), _3, _4}

and then generate a compiler error in check<> when the parameters don't
match.

Uli
--
C++ FAQ: http://parashift.com/c++-faq-lite

Sator Laser GmbH
Geschäftsführer: Thorsten Föcking, Amtsgericht Hamburg HR B62 932
Loading...