Variant is just a class
Josphe Brigmo
JospheBrigmo at gmail.com
Fri Sep 7 03:04:19 UTC 2018
On Thursday, 6 September 2018 at 20:25:18 UTC, Neia Neutuladh
wrote:
> On Thursday, 6 September 2018 at 10:18:43 UTC, Josphe Brigmo
> wrote:
>>
>> Variants can hold an arbitrary set of types.
>>
>> I imagine that it is effectively just a type id and an object
>> pointer!?
>
> It's a typeid and a static array large enough to hold any basic
> builtin type: the now-deprecated creal, a dynamic array, or a
> delegate.
>
> If you make a Variant from an object, it stores that object
> reference. The object reference is just a pointer, yes.
>
> If you make a Variant from a 256-byte struct, it copies that
> struct onto the heap and stores a pointer.
>
> If you make a Variant from a 6-byte struct, then it stores that
> struct and does no heap allocations.
>
>> If so, then it really is just a special type of a class class.
>
> It's similar to a java.lang.Object with explicit boxing, but
> without the need to create a new wrapper class for each value
> type.
>
>> It seems that variant and oop are essentially the same thing,
>> more or less, as whatever can be done in one can effectively
>> be done in the other, except, of course, that the class
>> version has compile time type information associated with it,
>> which sort of restricts variant to a subset of all types!?!
>
> Object-oriented programming includes inheritance and member
> function overloading. Variant doesn't; it's just about storage.
We are talking about two different things that are related:
A variant holds a set of objects. Using VariantClass limits the
types to a subset and allows for inherited types to be added.
Those objects may be classes which already have inheritance and
hence matching and calling their methods will dispatch
appropriately.
A variant sits on top of the object hierarchy, it is not
somewhere in the middle where objects will inherit from it(which
is impossible).
The difference is simply
Object x;
Variant y;
There is very little difference. If x is a class type then so
will variant hold class type and it will act just like the
Object does.
That is, Variant can do no worse than just being an object(except
it then becomes pointless as it can hold only one type.
> If you're working with classes, you'd be better off using a
> base class or interface instead of Variant for fields that can
> only hold objects of those types.
>
>> But variant can reduce the code complexity if one restricts
>> it's inputs to a specific class of types:
>
> Yes, for which you can use std.variant.Algebraic. For instance,
> Algebraic!(int, long, float) will accept ints, longs, and
> floats, but nothing else.
It is not the same since Algebraic does not allow inherited types
inside it's container? Or maybe it does?
VariantClass!X will accept anything derived from X and it will
dispatch appropriately since it just delegates to the normal
class dispatching mechanisms.
>
>> Then VariantClass will prevent any arbitrary type from being
>> assigned to the variant, effectively allow inheritance to be
>> used(in the sense that it will prevent any type from being
>> used at compile time, like inheritance):
>>
>> VariantClass!X v; // only T : X's are allowed.
>
> That's equivalent to `X v;` except with a wrapper around it.
>
Yes, but the whole point of the wrapper is simply to insure that
only a subset of types is used but allow for different types.
X v;
only allows types derived from X.
VariantClass!(X, Y) v;
allows types derived from X or from Y.
then matching on the type will simply delegate everything
appropriately.
If X and Y have a common type I then one can do
VariantClass!I v;
which would be the same as
I v;
But not the same as
Algebraic!I v;
because we couldn't stick in a derived object for I. We can cast,
and it works but it simply doesn't naturally allow derived types
for some reason.
VariantClass allows derived types. This is a big difference
because Algebraic doesn't naturally work well with oop but
VariantClass does.
>> Matching then is dispatch. We could further extend
>> VariantClass to return specific classes for each type that
>> dispatch to match and vice versa.
>
> I think you're saying that this sort of Algebraic could expose
> any methods and fields common to all its types?
>
>> Can D project interfaces like this?
>>
>> interface I
>> {
>> int foo(int);
>> }
>>
>> I i = project!I(o);
>
> You can write code to make that work. It would create a wrapper
> class that implements the requested interface, and that wrapper
> would just forward everything to the wrapped value.
>
> It would be a lot easier just to have the type implement the
> interface itself, if that's possible.
But this is just oop.
Opp requires more work to design because one has to implement the
interfaces in a prescribed way.
What I am talking about taking any object and if it has certain
methods that conform to some "interface" then it will behave as
if it were derived... even if it were not specified as derived.
Why this is better is because it allows objects that may not have
been inherited from some interface in the design(pre-existing).
interface I { int foo(int); }
class A
{
int foo(int);
}
DuckingVariant!I v;
A a;
v = a;
then
A, for all practical purposes is an I and v.foo can be called.
DuckingVariant simply checks if the type can be projected on to
I, which it can in this case.
Now, if A has meta info but no way to change the source, then
DuckingVariant could simply check if it matches the specified
interfaces(s) and if it does, do the mapping(which is just
forwarding).
Of course, if A changes the name of foo the everything will break
and since A doesn't implement I it is more prone to happen...
Also, such meta info isn't generally included in the binaries so
there is really no way to use DuckingVariant in general with
arbitrary code.
Remember, the idea is simply to wrap an object that has some
simple functionality needed such as implementing a few functions.
Seems using oop is overkill when it is not needed. Compile time
DbI would work fine but it doesn't work for runtime. So
DuckingVariant would sort of bridge the two by making DbI work at
run time, assuming it could peak at the meta info of the type.
If one starts getting to fancy then basically one is just
implementing COM with queryinterface and such.
>> Seems though this cannot be used at runtime though since
>> function signatures are not transported in the binary? If they
>> are, then maybe it would work and would reduce the overhead of
>> oop as one could just project types to other types that
>> overlap.
>
> You can use the witchcraft library on dub to perform runtime
> introspection, but there would be a performance penalty.
Cool, I will check it out. It seems it would allow DuckingVariant
to be created.
More information about the Digitalmars-d
mailing list