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.


A struct is defined in a single decl in the YAML file.

- decl: struct Cstruct1 {
          int ifield;
          double dfield;


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 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.


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
    void Method1() {};

And is wrapped in the YAML as:

- decl: class Class1
  - decl: int Method1()


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.



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
    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.


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:

- decl: class Class1
     foo: True

- decl: class Class2
  - decl: void accept1(Class1 & arg1)

- decl: class Class1
  - 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:

- decl: class Class1
  - decl: Class1()         +name(new)
      function_suffix: _default
  - decl: Class1(int flag) +name(new)
    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
   int m_flag;
   int m_test;

It is added to the YAML file as:

- decl: class Class1
  - 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.