• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] Struct interface/inheritance problem

Status
Not open for further replies.
Level 15
Joined
Aug 7, 2013
Messages
1,337
Hi,

Suppose I am trying to create an object (in the sense of struct/class/interface) which has many different instantiations, as shown below.

JASS:
struct ant
  …
  method eat takes unit food returns nothing
  //an implementation specific to this animal
  …
  endmethod
endstruct

struct bear
  …
  method eat takes unit food returns nothing
  //an implementation specific to this animal
  …
  endmethod
endstruct

struct clam
  …
  method eat takes unit food returns nothing
  //an implementation specific to this animal
  …
  endmethod
endstruct

While ant, bear, clam have different implementations of eat, they all essentially are the same object: an animal. Normally the solution would be simple: create an interface animal and give it an eat method. Then have each of the structs implement that interface for some OOP.

However, suppose I also give each animal another method, one that is the same, but will have different arguments across each animal.

JASS:
struct ant implements animal
  method foo takes nothing returns nothing
  …
  method eat takes unit food returns nothing
  //an implementation specific to this animal
  …
  endmethod
endstruct

struct bear implements animal
  method foo takes arg1 returns nothing
  …
  method eat takes unit food returns nothing
  //an implementation specific to this animal
  …
  endmethod
endstruct

struct clam extends animal
  method foo takes arg1, arg2, arg3 returns nothing
  …
  method eat takes unit food returns nothing
  //an implementation specific to this animal
  …
  endmethod
endstruct

where argN is any valid vJASS parameter, and for each struct implementing anima, it is not necessarily true that animalA_foo_argN == animalB_foo_argN.

So now if I had an array of animals like this:

JASS:
animal array animals[3] //where 0 is an ant, 1 is a bear, and 2 is a clam
…
animals[1].eat(deer) 
//no error, the interface method takes a single argument of type unit
animals[0].foo() 
//error, only one implementation of method foo possible

I would get an error, because animal interface only accepts a single unique set of parameters for a method name.

So I cannot use an interface for this kind of polymorphism. What about extending an animal struct?

Well, it will run into the same problem--while I can stub a parent method foo(), the arguments still have to be the same.

So what should I do? I think one thing might be to make each of the possible foos stub methods in a parent struct like so

JASS:
struct animal
  …
  stub method fooAnt takes nothing returns nothing
  endmethod
  stub method fooBear takes arg1 returns nothing
  endmethod
  stub method fooClam takes arg1, arg2, arg3 returns nothing
  endmethod
  …
endstruct

and then only know to call/implement the appropriate method, e.g.

JASS:
struct ant extends animal
  method fooAnt takes nothing returns nothing
  //an implementation specific to ant
  ...
  endmethod
endstruct

Any better solution/thoughts? One downside is if I have N child structs, each one will essentially inherit N-1 methods they cannot/should never use. A waste of space, but it allows me to do stuff like

JASS:
animal array animals[3]

set animals[0] = ant.create()
call animals[0].antFoo() //ok, no error here
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Use a cast:

JASS:
interface Base
	method foo takes nothing returns nothing
endinterface

struct Derived extends Base
	method foo takes nothing returns nothing
	
	endmethod
	
	method bar takes integer i returns nothing
		call BJDebugMsg(I2S(i))
	endmethod
endstruct

scope Test initializer onInit
	private function onInit takes nothing returns nothing
		local Base array container
		local Derived d = Derived.create()

		set container[0] = d
		call Derived(container[0]).bar(17) // Cast Base to Derived
	endfunction
endscope

As bar is different for each Derived class it wouldn't make much sense to inherit it uniformly from a Base class.

However this is not safe, as you have remember which child class is on which position in the array to cast them correctly, so I wouldn't recommend to do this.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
that is good solution, but you cannot guarantee that container[0] is of type Derived, what if it is of type Derived2? As in, imagine this:

JASS:
interface animal
    method eat takes nothing returns nothing
endinterface

struct Ant extends animal
    method eat takes nothing returns nothing
        call BJDebugMsg("Ant is eating!")
    endmethod
    
    method foo takes nothing returns nothing
        
    endmethod
endstruct

struct Bear extends animal
    method eat takes nothing returns nothing
        call BJDebugMsg("Bear is eating!")
    endmethod
    
    method foo takes integer a returns nothing
        
    endmethod
endstruct

struct Yetti extends animal
    method eat takes nothing returns nothing
        call BJDebugMsg("Yetti is eating!")
    endmethod
    
    method foo takes integer a, integer b, integer c returns nothing
        
    endmethod
endstruct

function Tester takes animal myAnimal returns nothing
    call myAnimal.foo() //no way
endfunction

You cant call foo from there, since you dont even know who myAnimal really is.

Fortunarely, Vexorian was thinking maybe a bit ahead of this, and he implemented what is known as typeid. Every struct that inherits interface gets unique integer to it per inerface.

This means that you can compare the type passed into function to your type. Please not ethat getType() must be called on instance, so thats why there is cast from 0 to the type.

Working example:

JASS:
interface animal
    method eat takes nothing returns nothing
endinterface

function Common takes string str returns nothing
    call BJDebugMsg(str)
endfunction

struct Ant extends animal
    method eat takes nothing returns nothing
        call BJDebugMsg("Ant is eating!")
    endmethod
    
    method foo takes nothing returns nothing
        call Common("thistype")
    endmethod
endstruct

struct Bear extends animal
    method eat takes nothing returns nothing
        call BJDebugMsg("Bear is eating!")
    endmethod
    
    method foo takes integer a returns nothing
        call Common("thistype")
    endmethod
endstruct

struct Yetti extends animal
    method eat takes nothing returns nothing
        call BJDebugMsg("Yetti is eating!")
    endmethod
    
    method foo takes integer a, integer b, integer c returns nothing
        call Common("thistype")
    endmethod
endstruct

function Tester takes animal myAnimal returns nothing
    local integer typeId = myAnimal.getType()
    if typeId == Yetti.typeid then
        //this is Yetti
        call Yetti(myAnimal).foo(0, 1, 2)
    elseif typeId == Bear.typeid then
        //this is Bear
        call Bear(myAnimal).foo(5)
    elseif typeId == Ant.typeid then
        //this is Ant
        call Ant(myAnimal).foo()
    endif
endfunction

struct s extends array
    private static method onInit takes nothing returns nothing
        local animal myAnimal = Yetti.create()
        call myAnimal.eat()
        call Tester(myAnimal)
    endmethod
endstruct
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
@edo: Nice example :)

@sethmachine: The problem with those approaches is that they somehow kill the purpose of an interface. Usually you want to provide an interface to the user so that he can implement/extend the features that he likes to have, but still use them with your algorithms. However, as foo is not part of the interface (and its not possible to make it part, because you can't know the call-signature of the function that the user likes), there is no guarantee that childs of Base even have this method.

You can somehow solve this using dynamic casts as edo showed you, but the problem is that you have to know all possible childs of Base. This is of course not possbile if your algorithms have to work with client code. Therefore I wouldn't recommend to do this.

I would do it like this:

JASS:
interface Base
	method foo takes nothing returns nothing
endinterface

struct Derived1 extends Base
	private integer int

	static method create takes integer i returns thistype
		local thistype this = thistype.allocate()
		set this.int = i
		return this
	endmethod

	method foo takes nothing returns nothing
		// Is used like foo(integer)
		call BJDebugMsg("thistype called with int = " + I2S(int))
	endmethod
endstruct

struct Derived2 extends Base
	private string str
	private real re

	static method create takes string s, real r returns thistype
		local thistype this = thistype.allocate()
		set this.str = s
		set this.re = r
		return this
	endmethod

	method foo takes nothing returns nothing
		// Is used like foo(string, real)
		call BJDebugMsg("thistype called with str = " + str + " and re = " + R2S(re))
	endmethod
endstruct

scope Test initializer onInit
	private function onInit takes nothing returns nothing
		local Derived1 d1 = Derived1.create(42)
		local Derived2 d2 = Derived2.create("my_str", 1337.0)
		local Base array container
		
		set container[0] = d1
		set container[1] = d2
		
		// We can now call foo uniformly for all childs of Base
		call container[0].foo()
		call container[1].foo()
	endfunction
endscope

Pass the required arguments in the constructor of Derived and store them. By doing so you have various advantages:

  • You can use any "parameters" in foo, just pass them to the constructor.
  • You can now call foo uniformly for all childs of Base because...
  • ... any derived struct of Base is guaranteed to have the method foo implemented.
  • No more casts are required (casts are error prone)
  • Your algorithms will run with any client derived struct that implements Base
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
However, as foo is not part of the interface (and its not possible to make it part, because you can't know the call-signature of the function that the user likes), there is no guarantee that childs of Base even have this method.

JASS:
struct A
    method myMethod takes nothing returns nothing
        call BJDebugMsg("Hey")
    endmethod
endstruct

function myFunc takes nothing returns nothing
    local A a = A.create()
    static if A.myMethod.exists() then
        call a.myMethod()
    endif
endfunction

not very good, but you can get if the struct has given function(of course you need to know the type of struct beforehand).

Also you cant get the signatures(return type, arguments) so this is quite limited.

What LFH wrote is most likely best solution
 
Status
Not open for further replies.
Top