Interfacing to C++ (GNAT Reference Manual)

### 14.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 successfully, 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`.