Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Categories

Lists of mixed types

NorseMNNorseMN Member Posts: 3
Having read a few of the threads on this forum, I have a glimmer of hope that there might be a few of you out there that can answer my question.

I confess that my experience with C++ is limited primarily to reading about it and some experimental tinkering. However, with decades of embedded C programming experience, and a long held interest in C++, I think I have a fair grasp of the basics.

I will try to state the issue as simply as possible, and hope that a useful response is possible without first providing a litany of whys and wherefores to describe the context. It's not that I am adverse to sharing more information. I suspect some of you might even find it interesting. But in the interest of efficiency, I'll try first to just get to the point. We can digress later, as much desired.

My problem involves managing lists of value instances of different value types. In theory, the set of value types could be limited to the standard numeric types ( char, unsigned char, int, etc. ). But ultimately, I'd like to be able to include derived types as well. And, toughest of all, I'd like to be able to dynamically ( at run-time ) specify a new value type, so an instance of the new type can also appear in a list. However, the problem is represented well enough by standard types, and if necessary, I could live with a fixed list of value types that are known at compile time.

It seems to me that in C or C++, the only way to make a list of mixed types is to first make them all the same type. This can be done many ways, but in C++ it generally boils down to up-casting a derived class pointer to a base class pointer and then making a list of base class pointers. ( A similar, but more manual technique in C casts addresses to void pointers. )

My goal is to find the most direct and efficient means to access a value instance in the list, as its actual type. I understand that, if I know the type, I can down-cast it and use RTTI to assure that the cast is valid. But I'm not satisfied with this. I want to write code that uses a variable type ID value to determine the type. In effect, I want each value instance to know its own type and behave accordingly.

If I have a list of value instances of a specific type ( for example, int ), I can easily create operator [] to provide a reference to any value instance in the list, given an instance ID number. This is extremely convenient, because the value reference can be used as an l_value or an r_value. This pretty much provides normal access to the value instance and supports all its normal behaviors. If the list contained a derived type that, for example, overrides the multiply operator, then that operation would be performed correctly.

It is also possible to associate a type ID number with each value type. This is essentially what RTTI does, and I could potentially use that, or roll my own method. However, it seems that the only way to restore a value to it's actual type, using a type ID number, is by trial and error. That is, some method must be provided to cast a base pointer to successive derived types until a cast succeeds. Worse yet, this must be done within a function that has a fixed return type. So, the result is known only within the function and cannot be "returned".

As an example, assume that:
- A base class is named Number.
- Derived classes named Int, Long and Fraction support value instances of three value types.
- A list of value instances is created for each value type.
- Value instances can be added to each list using a member function and they can be accessed by operator[].
- Another list is created for the lists of value instances, with the address of each list up-cast to a Number pointer.
- The Number pointers are accessed by operator[] with a type ID number as a subscript. ( i.e. Int = 1, Long = 2, Fraction = 3 ).
- A TypInstRef class is created to contain data for value ID and instance ID.
- Finally, a list of TypInstRef instances is created, with capabilities similar to the other lists. In effect, this is a list of mixed types.

Now, assume that I want to access the value associated with type ID = 1 and instance ID = 7.
When I use 1 to access the list of Int values, I get a Number pointer.
I have to cast the Number pointer back to an Int list, before I can access instance 7 of the list ( as an Int value ).
How can I directly cast it, based on the type ID = 1?

I would expect that, ideally, each class derived from the Number class has a static data member that holds its ID value.
This means that the ID value is not visible within a Number instance ( base class ).
So, the Number must be down-cast to a derived class, in order to even test whether the cast is correct.
( I suspect that this is the methodology behind RTTI and C++ casting operators. )
If the type ID is included as a data member of of the Number class, it would be known before the cast. ( An expensive choice in an embedded application. )
But this doesn't help much anyway, because I can't cast a Number pointer to a "1", and still have to somehow transform the type ID to a type cast.

I have imagined many other scenarios, but they all seem to simply move the problem to a different point in the process.
The latest possibility that caught my eye was a form of template definition that contains a numeric constant as a parameter.
Is it possible that I can somehow use this to create my own cast operator for the TypInstRef class?
I'm guessing not, because my instance ID value is not a constant.
However, my understanding of templates is still a little foggy.
Especially when used within a member class. ( A template class defined as a member of an enclosing class. )

I think that one option is to define a huge set of operators for the TypInstRef class to manage every combination ( permutation? ) of value types. This is a very ugly solution for many reasons. Probably the worst thing is that the class must be edited to change the list of supported types.

Perhaps someone can tell me that this is a well-known shortcoming ( or intentional roadblock ) of the C++ language and I need to re-think my design.
Or, perhaps someone knows of a technique that will help me.

When responding, please try to overcome the desire to lecture me on the potential dangers of casting.
Assume for the moment that it is possible for my system design to assure that no invalid casting can occur.
Everything is controlled so that nothing can go wrong... go wrong... go wrong...

I apologize for writing such a long entry.
I just hope it's adequate to prompt a helpful response, or at least lead to some interesting exchanges.
Thanks in advance.


Comments

  • LundinLundin Member Posts: 3,711
    My spontaneous comment, which you probably won't like, is that you are making things way too complicated. It is an easy thing to do in an object-oriented language. Keeping things simple has many benefits: speed, memory consumption and code that is easy to read and maintain.

    To get what you want, you only need this:

    [code]
    typedef enum
    {
    INT_TYPE,
    LONG_TYPE,
    FRACTION_TYPE,
    ...

    } ID_Type;

    typedef struct
    {
    void* data;
    ID_Type type;
    int size;

    } ListNode;[/code]


    Yes, it is C. The main benefit of C is that it works on almost every compiler. There are very few embedded compilers that support the whole C++ language. For example: without pondering too long I can come up with 3 well-known embedded C++ compilers that don't even support RTTI.

    If C isn't an option, the pretty, although quite inefficient C++ solution would be to make a generic STL vector which accepts any type. Generic templates give poor performance and you would probably have to store the type ID somewhere too, in a second vector for example.

    Also, creating types in runtime doesn't make sense. Types only exist in the programmer's world which is the source code. In the binary executable, there are no types.
  • NorseMNNorseMN Member Posts: 3

    Thank you for taking the time to read through my long-winded query.
    I understand your comments and appreciate what you are saying.
    However, I also have justifications for my elaborate plans.

    I am trying to create an application framework for programming embedded applications. I think that C++ has been around long enough now and is supported by enough embedded compilers that it is reasonable to choose it over C. I do NOT intend to use ALL the features and capabilities of C++, but probably more that are allowed by the typical subset that is called "Embedded C++". For example, I think that judiciously used templates could be extremely helpful. But, exception handling and garbage collection are probably out.

    I suspect that I will not use RTTI, unless I find some compelling advantage. So far, I don't see one and expect I will use an approach similar to your example. In fact, I don't see a lot of difference between your example and my plan, with respect to determining a value type.

    The reason my version is in a fancier package is that I want to use the lists of TypInstRef values as interface descriptions. I will build the lists into a hierarchy that acts as a "Data Directory" of a controller. Then, a Service Tool program, running in a PC, can communicate with any application program and present the data much like Windows Explorer presents files and folders. Controllers can similarly communicate with each other. A controller can even check at power-up, whether an optional controller is present (on a CAN bus) and setup the transmit and receive data lists to share data with it. There is far more to my plan than is practical to discuss.

    The simple example you suggest sets up a similar situation, but you don't take it far enough to get to the issue I'm trying to resolve. Let's see what happens when I try to use your ListNode structure in the way I'd like:

    [code]:
    // ...follows your declarations.
    ListNode LongA, IntB, IntC;

    // Initialize ListNode data appropriately to match names.
    ...

    // Try to use values as their actual types.
    LongA = IntB * IntC; // Error! This does not work!
    [/code]:

    Of course, this is inappropriate use of a C structure. But, if ListNode was a C++ object, it could be given appropriate behavior. However, it would be necessary to define each operator for each combination of value types.

    What I want is the ability to do something like:
    self_cast(LongA) = self_cast(IntB) * self_cast(IntC);
    Where self_cast() converts the argument to it's actual type.
    The advantage is that each type can then perform according to it's own defined behavior.

    Some of the lists in my Data Directory are lists of process functions. Each function takes a single argument, which is a TypInstRef value. This can give it access to a hierarchy of values. Essentially, a function interface is defined by a list of values, including sublists. The interface and the process function can be "plugged into" the Data Directory.

    There are many reasons for the "background lists" of instances for each type. Partly, this is tied into memory allocation and management. Another significant feature is that only one instance exists of each value, but it can be referenced in multiple lists.

    Fault management is handled using a branch of the hierarchy. Any process can report faults. Any process can see which faults are present. Initiation of a fault can add a process to a list (or stop one). Actually, a "component" can provide a process function and an interface that includes its fault list. A reference to its list can be added as an item in a higher level fault list. ... OK, too much already!

    The structure you suggest is a common way of managing streamed data. I could take that approach. And, I could probably even override the C++ streaming operators. But, I see a lot of overhead if I stream interfaces in and out of each function that is called. I already have all the data organized in a pattern that I can manage. I'd like functions to be able to access the data "in situ".

    To get back to my question...
    Given your data structure, and the following declaration...
    [/code]
    typedef struct
    {
    void* data;
    ID_Type type;
    int size;

    } ListNode;

    int iValue;
    ListNode myInt = { (void*)&iValue, INT_TYPE, sizeof(int) };
    [/code]

    How can I use ListNode as an int value, at the same scope where it is declared?

    I can write a function that can switch on ID_Type and cast void* to the assigned type. But, that function cannot return the result, because it must have a fixed return type. It cannot return either an int or a long. If it returns a ListNode, I'm right back where I started.

    Does this clarify my question?
    Am I completely nuts?

Sign In or Register to comment.