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); 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;
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); 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