Conversion system for options

I wrote a variant class that will be used as the main type in a dynamic language, which ultimately allows you to use 256 different types of values ​​(the header is an unsigned byte, in fact only 20 are used). Now I want to implement casting / conversion between types.

My initial thought was a look-up table, but the amount of memory shift that would be needed is not practical to implement.

What are the alternatives? Now I am considering three more methods from research and other people's suggestions:

  • Group types into larger subsets, such as numeric or collectible or others.
  • Create a transform interface that has the methods CanCast (from, to) and Cast (Variant) and allows classes that implement this interface to be added to the list, which can then be checked to see if any of the transform classes can be cast.
  • Similar to (1), but it creates several basic types, and casting is a two-stage process from the initial type to the master type, and then again to the final type.

What would be the best system?

Edit: I added generosity as I'm still not sure about the best system, the current answer is very good and definitely got my +1, but there should be people who have done this and can say that the best method is.

+4
source share
3 answers

My system is very "heavy" (a lot of code), but very fast and very multifunctional (cross-platform C ++). I'm not sure how far you would like to go with your design, but here are the biggest parts of what I have done:

DatumState is a class containing an "enumeration" for a "type", plus an eigenvalue that is a "union" among all primitive types, including void* . This class is not associated with all types and can be used for any native / primitive and reference to void* types. Since " enum " also has the context of " VALUE_OF " and " REF_TO ", this class can be represented as "fully containing" a float (or some primitive type) or "referencing-but-not-owning" a float (or some primitive type ) (Actually, I have the contexts " VALUE_OF ", " REF_TO " and " PTR_TO ", so I can logically store the value, reference-not-be-null-null or pointer-that-may-be-null-or-not , and I know that I need to delete-no-no.)

Datum is a class that fully contains DatumState , but extends its interface to accommodate various "well-known" types (for example, MyDate , MyColor , MyFileName , etc.). These known types are actually stored in void* inside the DatumState member. However, since the < enum " DatumState has the context" VALUE_OF "and" REF_TO ", it can represent" pointer-to-MyDate "or" value-of-MyDate ".

DatumStateHandle . Helper class parameterized using a known type (for example, MyDate , MyColor , MyFileName , etc.). This is the accessor used by Datum to retrieve state from a known type. The default implementation works for most classes, but any class with specific access semantics simply redefines its specific parameterization / implementation of the template for one or more member functions in this template class.

Macros, helper functions, and some other supporting stuff . To make it easier to “add” known types to my Datum / Variant , it was convenient for me to centralize the logic into several macros, provide some support functions like operator overloading, and establish some other conventions in my code.

As a “side effect” of this implementation, I got a ton of benefits, including reference and value semantics, null options for all types, and support for heterogeneous containers for all types.

For example, you can create a set of integers and index them:

 int my_ints[10]; Datum d(my_ints, 10/*count*/); for(long i = 0; i < d.count(); ++i) { d[i] = i; } 

Similarly, some data types are indexed by row or by enumeration:

 MyDate my_date = MyDate::GetDateToday(); Datum d(my_date); cout << d["DAY_OF_WEEK"] << endl; cout << d[MyDate::DAY_OF_WEEK] << endl; // alternative 

I can store element sets (initially) or set-of- Datum (wrapping each element). In any case, I can "deploy" recursively:

 MyDate my_dates[10]; Datum d(my_dates, 10/*count*/); for(long i = 0; i < d.count(); ++i) { cout << d[i][MyDate::DAY_OF_WEEK] << endl; } 

It can be argued that my semantics " REF_TO " and " VALUE_OF " are superfluous, but they are necessary for "set-unwrapping".

I did this “ Variant ” thing with nine different projects, and my current one is “hardest” (most code), but the one I like best (almost the fastest with a small object size), and I condemned the other eight projects for my use.

"Disadvantages" for my design:

  • Objects are accessed via static_cast<>() from void* (a safe type and quite quickly, but indirectness is required; but, a side effect is that the design supports " null " storage.)
  • Compilations are longer due to known types that are exposed through the Datum interface (but you can use DatumState if you don't need known type APIs.)

Whatever your design, I would recommend the following:

  • Use " enum " or something to say you are "type", separate from "cost." (I know that you can compress them into a single " int " or something with bitmap packaging, but it's slow access and very difficult to maintain as new types are introduced.)

  • Rely on templates or something to centralize operations, with a mechanism for specific types (override) (assuming you want to handle non-trivial types).

The name of the game is “simplified maintenance when adding new types” (or at least it was for me). As a good thermal article, it’s a very good idea if you rewrite, rewrite, rewrite, hold, or increase your functionality, because you constantly delete the code necessary to maintain the system (for example, minimize the effort required to adapt new types to the existing Variant infrastructure).

Good luck

+1
source

Made something like that.

You can add another byte to the header by specifying the type that it actually stores.

An example in a C-style programming language:

 typedef enum VariantInternalType { vtUnassigned = 0; vtByte = 1; vtCharPtr = 2; // <-- "plain c" string vtBool = 3; // other supported data types } // --> real data typedef struct VariantHeader { void* Reserved; // <-- your data (byte or void*) VariantInternalType VariantInternalType; } // --> hides real data typedef byte[sizeof(VariantHeader)] Variant; // allocates & assign a byte data type to a variant Variant ByteToVar(byte value) { VariantHeader MyVariantHeader; Variant MyVariant; MyVariantHeader.VariantInternalType = VariantInternalType.vtByte; MyVariantHeader.Reserved = value; memcpy (&MyVariant, &MyVariantHeader, sizeof(Variant)); return myVariant; } // allocates & assign a char array data type to a variant Variant CharPtrToVar(char* value) { VariantHeader MyVariantHeader; Variant MyVariant; MyVariantHeader.VariantInternalType = VariantInternalType.vtByte; MyVariantHeader.Reserved = strcpy(value); // copy exposed struct type data to hidden array data memcpy(&MyVariant, &MyVariantHeader, sizeof(Variant)); return myVariant; } // deallocs memory for any internal data type void freeVar(Variant &myVariant) { VariantHeader MyVariantHeader; // copy exposed struct type data to hidden array data memcpy(&MyVariantHeader, &MyVariant, sizeof(VariantHeader)); switch (MyVariantHeader.VariantInternalType) { case vtCharPtr: strfree(MyVariantHeader.reserved); break; // other types default: break; } // copy exposed struct type data to hidden array data memcpy(&MyVariant, &MyVariantHeader, sizeof(Variant)); } bool isVariantType(Variant &thisVariant, VariantInternalType thisType) { VariantHeader MyVariantHeader; // copy exposed struct type data to hidden array data memcpy(&MyVariantHeader, &MyVariant, sizeof(VariantHeader)); return (MyVariant.VariantInternalType == thisType); } // ------- void main() { Variant myVariantStr = CharPtrToVar("Hello World"); Variant myVariantByte = ByteToVar(42); char* myString = null; byte myByte = 0; if isVariantType(myVariantStr, vtCharPtr) { myString = VarToCharPtr(myVariantStr); // print variant string into screen } // ... } 

This is only an assumption, and it has not been verified.

+1
source

You may have already done the calculation, but the amount of memory you need for the lookup table is not so much.

If you just need to check if the types are compatible, then you need (256 * 256) / 2 bits. This requires 4k memory.

If you also need a pointer to a conversion function, you will need (256 * 256) / 2 pointers. This requires 128 thousand. Memory on a 32-bit machine and 256 thousand. On a 64-bit machine. If you want to make a low-level address layout, you can probably bring it to 64k on 32-bit and 64-bit machines.

0
source

All Articles