13.2 Interfacing to C++

The interface to C++ makes use of the following pragmas, which are primarily intended to be constructed automatically using a binding generator tool, although it is possible to construct them by hand.

Using these pragmas it is possible to achieve complete inter-operability between Ada tagged types and C++ class definitions. See Implementation Defined Pragmas, for more details.

pragma CPP_Class ([Entity =>] LOCAL_NAME)

The argument denotes an entity in the current declarative region that is declared as a tagged or untagged record type. It indicates that the type corresponds to an externally declared C++ class type, and is to be laid out the same way that C++ would lay out the type.

Note: Pragma CPP_Class is currently obsolete. It is supported for backward compatibility but its functionality is available using pragma Import with Convention = CPP.

pragma CPP_Constructor ([Entity =>] LOCAL_NAME)

This pragma identifies an imported function (imported in the usual way with pragma Import) as corresponding to a C++ constructor.

A few restrictions are placed on the use of the Access attribute in conjunction with subprograms subject to convention CPP: the attribute may be used neither on primitive operations of a tagged record type with convention CPP, imported or not, nor on subprograms imported with pragma CPP_Constructor.

In addition, C++ exceptions are propagated and can be handled in an others choice of an exception handler. The corresponding Ada occurrence has no message, and the simple name of the exception identity contains Foreign_Exception. Finalization and awaiting dependent tasks works properly when such foreign exceptions are propagated.

It is also possible to import a C++ exception using the following syntax:

LOCAL_NAME : exception;
pragma Import (Cpp,
  [Entity =>] LOCAL_NAME,
  [External_Name =>] static_string_EXPRESSION);

The External_Name is the name of the C++ RTTI symbol. You can then cover a specific C++ exception in an exception handler. If the string ends with “‘Class”, as if referencing the Class attribute of the C++ type, that enables “class-wide” type matching, i.e., instances of C++ classes derived from the one denoted by the RTTI symbol, that would be caught by C++ handlers for that type, will also be caught by Ada handlers for Entity. For non-class-wide RTTI symbols imported from C++, only exact type matches will be handled. C++ rethrown (dependent) exceptions are not distinguishable from the corresponding primary exceptions: they are handled exactly as if the primary exception had been raised.

With imported exceptions, especially with base-type matching, a single handled_sequence_of_statements may have exception handlers with choices that cover the same C++ types in ways that GNAT cannot detect. For example, C++ classes base and derived may be imported as exceptions with base-type matching, but GNAT does not know that they are related by inheritance, only the runtime will know it. Given:

exception
  when Derived_Exception => null;
  when Base_Exception => null;
  when others => null;

the earliest handler that matches the type of the raised object will be selected. If an instance of derived or a further derived type is raised, the first handler will be used; if an instance of base that is not an instance of derived is raised, the second handler will be used; raised objects that are not instances of base will be handled by the others handler. However, if the handlers were reordered (others must remain last), the Derived_Exception handler would never be used, because Base_Exception would match any instances of derived before Derived_Exception or others handlers were considered. Mixing exact-type and base-type matching exceptions may also involve overlapping handlers that GNAT will not reject: an exact-type Base_Only_Exception handler placed before Base_Exception will handle instances of base, whereas instances of derived types will be handled by Base_Exception. Swapping them will cause Base_Exception to handle all instances of base and derived types, so that a subsequent handler for Base_Only_Exception will never be selected.

The C++ object associated with a C++ Exception_Occurrence may be obtained by calling the GNAT.CPP_Exceptions.Get_Object_Address function. There are convenience generic wrappers named Get_Object, Get_Access_To_Object, and Get_Access_To_Tagged_Object, parameterized on the expected Ada type. Note that, for exceptions imported from C++, the address of the object is that of the subobject of the type associated with the exception, which may have a different address from that of the full object; for C++ exceptions handled by others handlers, however, the address of the full object is returned.

E.g., if the imported exception uses the RTTI symbol for the base class, followed by “‘Class”, and the C++ code raises (throws) an instance of a derived class, a handler for that imported exception will catch this Exception_Occurrence, and Get_Object_Address will return the address of the base subobject of the raised derived object; Get_Object, Get_Access_To_Object and Get_Access_To_Tagged_Object only convert that address to the parameterized type, so the specified type ought to be a type that imports the C++ type whose RTTI symbol was named in the declared exception, i.e., base, not derived or any other type. GNAT cannot detect or report if a type is named that does not match the handler’s RTTI-specified type.

For others handlers, and for exact type matches, the full object is obtained. The Get_Type_Info function that takes an Exception_Occurrence argument can be used to verify the type of the C++ object raised as an exception. The other Get_Type_Info function, that takes an Exception_Id, obtains the type expected by the handler, and no such type exists for others handlers. GNAT.CPP.Std.Name can then convert the opaque GNAT.CPP.Std.Type_Info_Ptr access to std::type_info objects, returned by either Get_Type_Info function, to a C++ mangled type name.

If an Exception_Occurrence was raised from C++, or following C++ conventions, GNAT.Exception_Actions.Exception_Language will return EL_Cpp, whether the exception handler is an imported C++ exception or others. GNAT.Exception_Actions.Is_Foreign_Exception returns True for all of these, as well as for any case in which Exception_Language is not EL_Ada.

--  Given the following partial package specification:

  Base_Exception : exception;
  pragma Import (Cpp, Base_Exception, "_ZTI4base'Class");
  --  Handle instances of base, and of subclasses.

  type Base is limited tagged record
    [...]
  end record;
  pragma Import (Cpp, Base);

  type Derived is limited tagged record
    [...]
  end record;
  pragma Import (Cpp, Derived);

  type Unrelated is access procedure (B : Boolean);

  function Get_Base_Obj_Acc is
    new Get_Access_To_Tagged_Object (Base);
  function Get_Derived_Obj_Acc is
    new Get_Access_To_Tagged_Object (Derived);
  function Get_Unrelated_Obj_Acc is
    new Get_Access_To_Object (Unrelated);

  procedure Raise_Derived;
  --  Raises an instance of derived (with a base subobject).


--  The comments next to each statement indicate the behavior of
--  the following pseudocode blocks:

begin
  Raise_Derived;
exception
  when BEx : Base_Exception =>
    ?? := Is_Foreign_Exception (BEx);  --  True
    ?? := Exception_Language (BEx);    --  EL_Cpp
    ?? := Name (Get_Type_Info (BEx));  --  "7derived"
    ?? := Name (Get_Type_Info (Exception_Identity (BEx)));  --  "4base"
    ?? := Get_Object_Address (BEx);    --  base subobject in derived object
    ?? := Get_Base_Obj_Acc (BEx):      --  ditto, as access to Base
    ?? := Get_Derived_Obj_Acc (BEx):   --  ditto, NO ERROR DETECTED!
    ?? := Get_Unrelated_Obj_Acc (BEx): --  ditto, NO ERROR DETECTED!
end;


begin
  Raise_Derived;
exception
  when BEx : others =>
    ?? := Is_Foreign_Exception (BEx);  --  True
    ?? := Exception_Language (BEx);    --  EL_Cpp
    ?? := Name (Get_Type_Info (BEx));  --  "7derived"
    ?? := Get_Type_Info (Exception_Identity (BEx));  --  null
    ?? := Get_Object_Address (BEx);    --  full derived object
    ?? := Get_Derived_Obj_Acc (BEx):   --  ditto, as access to Derived
    ?? := Get_Base_Obj_Acc (BEx):      --  ditto, NO ERROR DETECTED!
    ?? := Get_Unrelated_Obj_Acc (BEx): --  ditto, NO ERROR DETECTED!
end;

The calls marked with NO ERROR DETECTED! will compile sucessfully, even though the types specified in the specializations of the generic function do not match the type of the exception object that the function is expected to return. Mismatches between derived and base types are particularly relevant because they will appear to work as long as there isn’t any offset between pointers to these types. This may hold in many cases, but is subject to change with various possible changes to the derived class.

The GNAT.CPP.Std package offers interfaces corresponding to the C++ standard type std::type_info. Function To_Type_Info_Ptr builds an opaque Type_Info_Ptr to reference a std::type_info object at a given System.Address.