Structs and Classes¶
Shroud supports both structs and classes. But it treats them much
differently. Whereas in C++ a struct and class are essentially the
same thing, Shroud treats structs as a C style struct. They do not
have associated methods. This allows them to be mapped to a Fortran
derived type with the bind(C)
attribute and a Python NumPy array.
Classes are wrapped by a shadow derived-type with methods implemented
as type-bound procedures in Fortran and an extension type in Python.
Struct¶
A struct is defined in a single decl
in the YAML file.
- decl: struct Cstruct1 {
int ifield;
double dfield;
};
Fortran¶
This is translated directly into a Fortran derived type with the
bind(C)
attribute.
type, bind(C) :: cstruct1
integer(C_INT) :: ifield
real(C_DOUBLE) :: dfield
end type cstruct1
All creation and access of members can be done using Fortran.
type(cstruct1) st(2)
st(1)%ifield = 1_C_INT
st(1)%dfield = 1.5_C_DOUBLE
st(2)%ifield = 2_C_INT
st(2)%dfield = 2.6_C_DOUBLE
Python¶
Python can treat struct in several different ways. First, treat it the same as a class. An extension type is created with descriptors for the field methods. Second, as a numpy descriptor. This allows an array of structs to be used easily. Finally, as a tuple of Python types.
PY_struct_arg class, numpy, list
When treated as a NumPy array no memory will be copied since the NumPy array contains a pointer to the C++ memory.
import cstruct
dt = cstruct.Cstruct1_dtype
a = np.array([(1, 1.5), (2, 2.6)], dtype=dt)
The descriptor is created in the wrapper NumPy Struct Descriptor.
Class¶
All problems in computer science can be solved by another level of indirection. — David Wheeler
Each class in the input file will create a struct which acts as a shadow class for the C++ class. A pointer to an instance is saved in the shadow class. This pointer is then passed down to the C++ routines to be used as the this instance.
Using the tutorial as an example, a simple class is defined in the C++ header as:
class Class1
{
public:
void Method1() {};
};
And is wrapped in the YAML as:
declarations:
- decl: class Class1
declarations:
- decl: int Method1()
Fortran¶
The Fortran interface will create two derived types. The first is
used to interact with the C wrapper and uses bind(C)
. The C
wrapper creates a corresponding struct. It contains a pointer to an
instance of the class and index used to release the instance.
The idtor
argument is described in Memory Management.
wrapftutorial.f
typesTutorial.h
The capsule is added to the Fortran shadow class. This derived type
can contain type-bound procedures and may not use the bind(C)
attribute.
type class1
type(SHROUD_class1_capsule) :: cxxmem
contains
procedure :: method1 => class1_method1
end type class1
A function which returns a class, including constructors, is passed a
pointer to a F_capsule_data_type. The argument’s members are filled
in by the function. The function will return a type(C_PTR)
which
contains the address of the F_capsule_data_type argument. The
interface/prototype for the C wrapper function allows it to be used in
expressions similar to the way that strcpy
returns its destination
argument.
A full example is at Constructor and Destructor.
Python¶
An struct is created for each C++ class.
The idtor
aregument is used to release memory and described at
Memory Management. The splicer allows additional fields
to be added by the developer which may be used in function wrappers.
Forward Declaration¶
A class may be forward declared by omitting declarations
.
All other fields, such as format
and options
must be provided
on the initial decl
of a Class.
This will define the type and allow it to be used in following declarations.
The class’s declarations can be added later:
declarations:
- decl: class Class1
options:
foo: True
- decl: class Class2
declarations:
- decl: void accept1(Class1 & arg1)
- decl: class Class1
declarations:
- decl: void accept2(Class2 & arg2)
Constructor and Destructor¶
The constructor and destuctor methods may also be exposed to Fortran.
The class example from the tutorial is:
declarations:
- decl: class Class1
declarations:
- decl: Class1() +name(new)
format:
function_suffix: _default
- decl: Class1(int flag) +name(new)
format:
function_suffix: _flag
- decl: ~Class1() +name(delete)
The default name of the constructor is ctor
. The name can
be specified with the name attribute.
If the constructor is overloaded, each constructor must be given the
same name attribute.
The function_suffix must not be explicitly set to blank since the name
is used by the generic
interface.
The constructor and destructor will only be wrapped if explicitly added
to the YAML file to avoid wrapping private
constructors and destructors.
The Fortran wrapped class can be used very similar to its C++ counterpart.
use tutorial_mod
type(class1) obj
integer(C_INT) i
obj = class1_new()
i = obj%method1()
call obj%delete
For wrapping details see Constructor and Destructor.
Member Variables¶
For each member variable of a C++ class a C and Fortran wrapper function will be created to get or set the value. The Python wrapper will create a descriptor:
class Class1
{
public:
int m_flag;
int m_test;
};
It is added to the YAML file as:
- decl: class Class1
declarations:
- decl: int m_flag +readonly;
- decl: int m_test +name(test);
The readonly attribute will not write the setter function or descriptor. Python will report:
>>> obj = tutorial.Class1()
>>> obj.m_flag =1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'm_flag' of 'tutorial.Class1' objects is not writable
The name attribute will change the name of generated functions and
descriptors. This is helpful when using a naming convention like
m_test
and you do not want m_
to be used in the wrappers.
For wrapping details see Getter and Setter.