Heres an example of what I call “macro templates” in PureBasic. A template encapsulates certain functionality, allowing you to dynamically generate the code in a flexible manner.
This example implements a bare-bones n-vector library using a structure and a static array:
Macro Vector_Register( _n, _type )
Structure VECTOR#_n#_type
vector._type[_n]
EndStructure
Procedure._type Vector#_n#_type#_Add( *r.VECTOR#_n#_type, *a.VECTOR#_n#_type, *b.VECTOR#_n#_type )
Define.i i
For i=0 To _n - 1
*r\vector[i] = *a\vector[i] + *b\vector[i]
Next
EndProcedure
Procedure._type Vector#_n#_type#_Subtract( *r.VECTOR#_n#_type, *a.VECTOR#_n#_type, *b.VECTOR#_n#_type )
Define.i i
For i=0 To _n - 1
*r\vector[i] = *a\vector[i] - *b\vector[i]
Next
EndProcedure
Procedure._type Vector#_n#_type#_Divide( *r.VECTOR#_n#_type, *a.VECTOR#_n#_type, *b.VECTOR#_n#_type )
Define.i i
For i=0 To _n - 1
If *b\vector[i] <> 0.0 And *a\vector[i] <> 0.0
*r\vector[i] = *a\vector[i] / *b\vector[i]
EndIf
Next
EndProcedure
Procedure._type Vector#_n#_type#_Multiply( *r.VECTOR#_n#_type, *a.VECTOR#_n#_type, *b.VECTOR#_n#_type )
Define.i i
For i=0 To _n - 1
*r\vector[i] = *a\vector[i] * *b\vector[i]
Next
EndProcedure
Procedure._type Vector#_n#_type#_DotProduct( *a.VECTOR#_n#_type, *b.VECTOR#_n#_type )
Define.i i
Define._type result
For i=0 To _n - 1
result + ( *a\vector[i] * *b\vector[i] )
Next
ProcedureReturn result
EndProcedure
Procedure._type Vector#_n#_type#_Length( *v.VECTOR#_n#_type )
ProcedureReturn Sqr( Vector#_n#_type#_DotProduct( *v, *v ) )
EndProcedure
Procedure._type Vector#_n#_type#_Distance( *a.VECTOR#_n#_type, *b.VECTOR#_n#_type )
Define.VECTOR#_n#_type temp
Vector#_n#_type#_Subtract( temp, *a, *b )
ProcedureReturn Vector#_n#_type#_Length( temp )
EndProcedure
Procedure.s Vector#_n#_type#_Debug( *v.VECTOR#_n#_type, Decimals.i=#PB_Default )
Define.i i
Define.s tmp = "["
For i=0 To _n-1
tmp + StrF( *v\vector[i], Decimals.i )
If i <> _n-1
tmp + ", "
EndIf
Next
tmp + "]"
Debug tmp
ProcedureReturn tmp
EndProcedure
EndMacro
It might look strange/complicated at first, but once you read it you’ll realize it’s fairly simple.
Let’s see the usage of this particular template:
Vector_Register(3, f ) ; Register a float "vec3".
Define.VECTOR3f a,b,c ; Define a few vectors with the new structure.
a\vector[0] = 10.0
a\vector[1] = 20.0
a\vector[2] = 30.0
b\vector[0] = 100.0
b\vector[1] = 200.0
b\vector[2] = 300.0
Vector3f_Add(c, a, b) ; c = a + b
Vector3f_Debug(c) ; show each element using the debug output.
Debug Vector3f_DotProduct( a, b )
Debug Vector3f_Length( a )
Debug Vector3f_Distance( a, b )
Cool, huh?. And you can define any amount of elements with any basic type.
Of course we sacrificed speed for flexibility. In those cases where we have to define n-vectors, this would be an ideal solution. For everything else, I suggest a specific library, such as my vec3 macro lib.
Using this principle you can abstract almost anything, within reason. One good example is my object factory template. Ideally one would have arrays, lists, etc. Implemented in this very same way, in such case the possibilities would be endless and you’d be able to define dynamic lists/arrays inside structures, etc.
I strongly advice you to implement at least one of those templates, even if it’s just for an exercise.
Having the extra tools can’t hurt!
Cheers.