+1  A: 

There's nothing inherently wrong with this kind of code. Delaying type-checking until runtime is perfectly valid, although you will have to work hard to defeat the compile-time type system. I wrote a homogenous stack class, where you could insert any type, which functioned in a similar fashion.

However, you have to ask yourself- what are you actually going to be using this for? I wrote a homogenous stack to replace the C++ stack for an interpreted language, which is a pretty tall order for any particular class. If you're not doing something drastic, this probably isn't the right thing to do.

In short, you can do it, and it's not illegal or bad or undefined and you can make it work - but you only should if you have a very desperate need to do things outside the normal language scope. Also, your code will die horrendously when C++0x becomes Standard and now you need to move and all the rest of it.

The easiest way to think of your code is actually a managed heap of a miniature size. You place on various types of object.. they're stored contiguously, etc.

Edit: Wait, you didn't manage to enforce type safety at runtime either? You just blew compile-time type safety but didn't replace it? Let me post some far superior code (that is somewhat slower, probably).

Edit: Oh wait. You want to convert your dynamic_struct, as the whole thing, to arbitrary unknown other structs, at runtime? Oh. Oh, man. Oh, seriously. What. Just no. Just don't. Really, really, don't. That's so wrong, it's unbelievable. If you had reflection, you could make this work, but C++ doesn't offer that. You can enforce type safety at runtime per each individual member using dynamic_cast and type erasure with inheritance. Not for the whole struct, because given a type T you can't tell what the types or binary layout is.

DeadMG
This has been yet another exercise in circumventing C++ to be more like the language I'm writing, because I'm much too lazy to actually just *work* on the language. Honestly. Anyway, one of the supposed advantages of something like this class is that it could be used to make automatic conversions simpler between structures that are semantically compatible but not binary-compatible, which I guess would be a boon when mixing old C APIs.
Jon Purdy
How would it help? You can only convert at the binary level. It would be easier to write your own cast operators - they don't have to be members, you know.
DeadMG
Yeah, type safety considerations gone out the window; very bad. I guess the simplest way to take care of it it to just store and test the `typeid` result for each member. Unchecked accesses would then be equivalent to plain old `reinterpret_cast`, if you happen to need that.
Jon Purdy
No. For each individual member, you can use inheritance and dynamic_cast to enforce type safety. What you can't do is support casting the dynamic_struct itself to any arbitrary type, as C++ won't give you enough information about T to tell if you can safely convert or not.
DeadMG
Simply having the ability to dynamically create structures that are binary-compatible with static structures could be useful enough. And how do I get `dynamic_cast` to play nice with POD members?
Jon Purdy
Write your own linked list class. For the nodes, inherit from base class, typename T. Store index into memory buffer in derived class. Map key type to base_node*. When accessing, dynamic_cast the resulting pointer to derived_node<T>*. If dynamic_cast succeeds, the memory at memory_buffer[ptr] is of type T, and you can guarantee the safety of the following static cast. Else, throw exception, because that's not the right type. Of course, this can be optimized much further if you know what you're doing. This can be expanded to form all necessary accesses, copy, destruct, etc.
DeadMG
Makes perfect sense. Well, there's the type safety issue more or less solved. Now, if I can just make sure the alignment isn't going to bite me later...
Jon Purdy
@Jon Purdy: It's not solved. That technique only works per member. You can't guarantee anything at all about converting the whole struct, because C++ doesn't provide the necessary compile-time or run-time reflection you'd need to determine if it's safe or not.
DeadMG
@DeadMG: C++ doesn't provide it, but @Potatoswatter's answer does, at least as well as can be hoped for.
Jon Purdy
@Jon Purdy: If you're going to manually write code for every potential struct, why not just write your own cast operator?
DeadMG
+1  A: 

I think the type-checking could be improved. Right now it will reinterpret_cast itself to any type with the same size.

Maybe create an interface to register client structures at program startup, so they may be verified member-by-member — or even rearranged on the fly, or constructed more intelligently in the first place.

#define REGISTER_DYNAMIC_STRUCT_CLIENT( STRUCT, MEMBER ) \
    do dynamic_struct::registry< STRUCT >() // one registry obj per client type \
        .add( # MEMBER, &STRUCT::MEMBER, offsetof( STRUCT, MEMBER ) ) while(0)
        //    ^ name as str ^ ptr to memb ^ check against dynamic offset
Potatoswatter
I was just thinking about macro-based solutions to this, myself, and yours is rather a good approach. It would involve separating the *description* of a `dynamic_struct` from the *instance*, but that was something that needed to be done anyway.
Jon Purdy
@Jon: No separation needs to occur at all… I was thinking that the `as` method, once it knows the destination of the cast, would consult the registry. Maybe reference-counted smart pointers could achieve partial separation, and the best of both worlds.
Potatoswatter
@Potatoswatter: My thoughts exactly: you've mistaken my meaning. As it stands, the `members` member of the `dynamic_struct` is, well, a member. It should be living statically in the registry and shared between `dynamic_struct` instances. That's all.
Jon Purdy
A: 

I have one question: what do you get out of it ?

I mean it's a clever piece of code but:

  • you're fiddling with memory, the chances of blow-up are huge
  • it's quite complicated too, I didn't get everything and I would certainly have to pose longer...

What I am really wondering is what you actually want...

For example, using Boost.Fusion

struct a_key { typedef char type; };
struct object_key { typedef Foo type; };

typedef boost::fusion<
  std::pair<a_key, a_key::type>,
  std::pair<object_key, object_key::type>
> data_type;

int main(int argc, char* argv[])
{
  data_type data;
  boost::fusion::at_key<a_key>(data) = 'a'; // compile time checked
}

Using Boost.Fusion you get compile-time reflection as well as correct packing.

I don't really see the need for "runtime" selection here (using a value as key instead of a type) when you need to pass the right type to the assignment anyway (char vs Foo).

Finally, note that this can be automated, thanks to preprocessor programming:

DECLARE_ATTRIBUTES(
  mData,
  (char, a)
  (char, b)
  (char, c)
  (int, i)
  (Foo, object)
)

Not much wordy than a typical declaration, though a, b, etc... will be inner types rather than attributes names.

This has several advantages over your solution:

  • compile-time checking
  • perfect compliance with default generated constructors / copy constructors / etc...
  • much more compact representation
  • no runtime lookup of the "right" member
Matthieu M.