Thread overview
Python's list equivalent with std.variant?
Oct 03, 2021
rjkilpatrick
Oct 03, 2021
Imperatorn
Oct 03, 2021
jfondren
Oct 04, 2021
Ali Çehreli
Oct 05, 2021
Kagamin
October 03, 2021

Hi.

I am trying to emulate Python's list in D, to store some derived classes

My initial thought was to use a dynamic array of std.variants.
I've tried implementing something but seeing as we can only instantiate templates at compile-time, and I may only know the type at run-time, I'm not sure how to go about this:

import std.stdio : writeln;
import std.variant;
import std.conv;

// Arbitrary super class
class SuperClass {
    this() {
    }
}

// Derived class with members
class DerivedClass : SuperClass {
public:
    this(float a) {
        this.a = a;
    }
    float a;
}

class OtherDerivedClass : SuperClass {}

void main() {
    // When we use `SuperClass[] list;` here, we find 'a' is hidden by the base class
    Variant[] list;

    // Attempting to append derived class instances to list
    list ~= new DerivedClass(1.0f);
    list ~= new OtherDerivedClass;

    list[0].a;
    list[0].to!(get!(list[0].type)).a.writeln;
}

And we get the error(s):

onlineapp.d(27): Error: cannot append type `onlineapp.DerivedClass` to type `VariantN!32LU[]`
onlineapp.d(28): Error: cannot append type `onlineapp.OtherDerivedClass` to type `VariantN!32LU[]`
onlineapp.d(30): Error: no property `a` for type `std.variant.VariantN!32LU`
onlineapp.d(31): Error: template `object.get` does not match any template declaration

I would be grateful for any help

October 03, 2021

On Sunday, 3 October 2021 at 22:22:48 UTC, rjkilpatrick wrote:

>

Hi.

I am trying to emulate Python's list in D, to store some derived classes

[...]

You would have to explicitly cast(Variant) when appending to your array.

But in the last example where you have list[0].a, that will only work statically if you can resolve that property. So you would have to either check the type or get out of the box and use some dynamic object

October 03, 2021

On Sunday, 3 October 2021 at 22:22:48 UTC, rjkilpatrick wrote:

>
void main() {
    // When we use `SuperClass[] list;` here, we find 'a' is hidden by the base class
    Variant[] list;

    // Attempting to append derived class instances to list
    list ~= new DerivedClass(1.0f);
    list ~= new OtherDerivedClass;

    list[0].a;
    list[0].to!(get!(list[0].type)).a.writeln;
}

This works:

void main() {
    Variant[] list;

    list ~= new DerivedClass(1.0f).Variant;
    list ~= new OtherDerivedClass().Variant;

    writeln(list[0].get!DerivedClass.a);
}

Parameters passed in !() need to be statically known, at compile-time, so get!(list[0].type) doesn't make sense with a runtime list.

If everything in the list is going to be a child of some class, then you don't need std.variant at all, you can just use OOP:

import std.stdio : writeln;

class SuperClass {
    this() {
    }
}

class DerivedClass : SuperClass {
public:
    this(float a) {
        this.a = a;
    }
    float a;
}

class OtherDerivedClass : SuperClass {}
class YetAnotherDerivedClass : SuperClass {}

void main() {
    SuperClass[] list;

    list ~= cast(SuperClass) new DerivedClass(1.0f);
    list ~= cast(SuperClass) new OtherDerivedClass;
    list ~= cast(SuperClass) new YetAnotherDerivedClass;

    writeln((cast(DerivedClass) list[0]).a);

    foreach (obj; list) {
        if (auto deriv = cast(DerivedClass) obj) {
            writeln("I found a DerivedClass: ", deriv.a);
        } else if (cast(OtherDerivedClass) obj) {
            writeln("I found an OtherDerivedClass");
        } else {
            writeln("I found an unexpected child: ", obj);
        }
    }
}

output:

1
I found a DerivedClass: 1
I found an OtherDerivedClass
I found an unexpected child: variant.YetAnotherDerivedClass

Object casts like that are null when the cast is invalid.

If you don't necessarily have a superclass, but still do have a definite number of possible member types, you can use std.sumtype:

import std.stdio : writeln;
import std.sumtype;

struct A { float a; }
struct B { }
struct C { }
alias Alpha = SumType!(A, B, C);

void main() {
    Alpha[] list;

    list ~= A(1.0f).Alpha;
    list ~= B().Alpha;
    list ~= C().Alpha;

    list[0].tryMatch!((A a) => writeln(a.a));

    foreach (obj; list) {
        obj.match!(
            (A a) => writeln("I found A(", a.a, ")"),
            (B _) => writeln("I found B"),
            (C _) => writeln("I found C"),
        );
    }
}

output:

1
I found A(1)
I found B
I found C
October 03, 2021
On 10/3/21 3:22 PM, rjkilpatrick wrote:

> I am trying to emulate Python's list in D, to store some derived classes

I notice your code comment about SuperClass[]. It is still the most obvious solution here. You just need to check the reuslt of a cast(DerivedClass):

import std.stdio : writefln;
import std.variant;
import std.conv;

// Arbitrary super class
class SuperClass {
     this() {
     }
}

// Derived class with members
class DerivedClass : SuperClass {
public:
     this(float a) {
         this.a = a;
     }
     float a;
}

class OtherDerivedClass : SuperClass {}

void main() {
  // When we use `SuperClass[] list;` here, we find 'a' is hidden by the base class

  // [Ali]: When you must know what the exact derived type you
  //        are using, generally there is a better approach.
  //
  //        Assuming that you really want to "downcast", then you
  //        simply cast to DerivedClass and see whether the
  //        pointer is null or not. (See below.)

  SuperClass[] list;

  // Attempting to append derived class instances to list
  list ~= new DerivedClass(1.0f);
  list ~= new OtherDerivedClass;

  foreach (i, item; list) {
    auto p = cast(DerivedClass)item;

    if (p) {
      writefln!"%s: Yes, a DerivedClass object with a == %s."(i, p.a);

    } else {
      writefln!"%s: No, not a DerivedClass object."(i);
    }
  }
}

Ali

October 05, 2021

On Sunday, 3 October 2021 at 22:22:48 UTC, rjkilpatrick wrote:

>
import std.stdio : writeln;
import std.variant;
import std.conv;

// Arbitrary super class
class SuperClass {
    this() {
    }
}

// Derived class with members
class DerivedClass : SuperClass {
public:
    this(float a) {
        this.a = a;
    }
    float a;
}

class OtherDerivedClass : SuperClass {}

void main() {
    // When we use `SuperClass[] list;` here, we find 'a' is hidden by the base class
    Variant[] list;

    // Attempting to append derived class instances to list
    list ~= new DerivedClass(1.0f);
    list ~= new OtherDerivedClass;

    list[0].a;
    list[0].to!(get!(list[0].type)).a.writeln;
}

Looks like you want full duck typing. Dynamic objects are just hashtables of properties, so an array of them is something like this:
Variant[string][] list;
Variant[string] obj;
obj["a"]=Variant(1.0f);
list[0]["a"].get!float.writeln;