Interfacing with C++ constructors

In order to interface with C++ constructors GNAT provides the pragma CPP_Constructor (see the GNAT_Reference_Manual for additional information). In this section we present some common uses of C++ constructors in mixed-languages programs in GNAT.

Let us assume that we need to interface with the following C++ class:

class Root {
  int  a_value;
  int  b_value;
  virtual int Get_Value ();
  Root();              // Default constructor
  Root(int v);         // 1st non-default constructor
  Root(int v, int w);  // 2nd non-default constructor

For this purpose we can write the following package spec (further information on how to build this spec is available in Interfacing with C++ at the Class Level and Generating Ada Bindings for C and C++ headers).

with Interfaces.C; use Interfaces.C;
package Pkg_Root is
  type Root is tagged limited record
     A_Value : int;
     B_Value : int;
  end record;
  pragma Import (CPP, Root);

  function Get_Value (Obj : Root) return int;
  pragma Import (CPP, Get_Value);

  function Constructor return Root;
  pragma Cpp_Constructor (Constructor, "_ZN4RootC1Ev");

  function Constructor (v : Integer) return Root;
  pragma Cpp_Constructor (Constructor, "_ZN4RootC1Ei");

  function Constructor (v, w : Integer) return Root;
  pragma Cpp_Constructor (Constructor, "_ZN4RootC1Eii");
end Pkg_Root;

On the Ada side the constructor is represented by a function (whose name is arbitrary) that returns the classwide type corresponding to the imported C++ class. Although the constructor is described as a function, it is typically a procedure with an extra implicit argument (the object being initialized) at the implementation level. GNAT issues the appropriate call, whatever it is, to get the object properly initialized.

Constructors can only appear in the following contexts:

In a declaration of an object whose type is a class imported from C++, either the default C++ constructor is implicitly called by GNAT, or else the required C++ constructor must be explicitly called in the expression that initializes the object. For example:

Obj1 : Root;
Obj2 : Root := Constructor;
Obj3 : Root := Constructor (v => 10);
Obj4 : Root := Constructor (30, 40);

The first two declarations are equivalent: in both cases the default C++ constructor is invoked (in the former case the call to the constructor is implicit, and in the latter case the call is explicit in the object declaration). Obj3 is initialized by the C++ non-default constructor that takes an integer argument, and Obj4 is initialized by the non-default C++ constructor that takes two integers.

Let us derive the imported C++ class in the Ada side. For example:

type DT is new Root with record
   C_Value : Natural := 2009;
end record;

In this case the components DT inherited from the C++ side must be initialized by a C++ constructor, and the additional Ada components of type DT are initialized by GNAT. The initialization of such an object is done either by default, or by means of a function returning an aggregate of type DT, or by means of an extension aggregate.

Obj5 : DT;
Obj6 : DT := Function_Returning_DT (50);
Obj7 : DT := (Constructor (30,40) with C_Value => 50);

The declaration of Obj5 invokes the default constructors: the C++ default constructor of the parent type takes care of the initialization of the components inherited from Root, and GNAT takes care of the default initialization of the additional Ada components of type DT (that is, C_Value is initialized to value 2009). The order of invocation of the constructors is consistent with the order of elaboration required by Ada and C++. That is, the constructor of the parent type is always called before the constructor of the derived type.

Let us now consider a record that has components whose type is imported from C++. For example:

type Rec1 is limited record
   Data1 : Root := Constructor (10);
   Value : Natural := 1000;
end record;

type Rec2 (D : Integer := 20) is limited record
   Rec   : Rec1;
   Data2 : Root := Constructor (D, 30);
end record;

The initialization of an object of type Rec2 will call the non-default C++ constructors specified for the imported components. For example:

Obj8 : Rec2 (40);

Using Ada 2005 we can use limited aggregates to initialize an object invoking C++ constructors that differ from those specified in the type declarations. For example:

Obj9 : Rec2 := (Rec => (Data1 => Constructor (15, 16),
                        others => <>),
                others => <>);

The above declaration uses an Ada 2005 limited aggregate to initialize Obj9, and the C++ constructor that has two integer arguments is invoked to initialize the Data1 component instead of the constructor specified in the declaration of type Rec1. In Ada 2005 the box in the aggregate indicates that unspecified components are initialized using the expression (if any) available in the component declaration. That is, in this case discriminant D is initialized to value 20, Value is initialized to value 1000, and the non-default C++ constructor that handles two integers takes care of initializing component Data2 with values 20,30.

In Ada 2005 we can use the extended return statement to build the Ada equivalent to C++ non-default constructors. For example:

function Constructor (V : Integer) return Rec2 is
   return Obj : Rec2 := (Rec => (Data1  => Constructor (V, 20),
                                 others => <>),
                         others => <>) do
      --  Further actions required for construction of
      --  objects of type Rec2
   end record;
end Constructor;

In this example the extended return statement construct is used to build in place the returned object whose components are initialized by means of a limited aggregate. Any further action associated with the constructor can be placed inside the construct.