Limitations

This document introduces all limitations encountered when trying to bind specific language constructs or types.

Polyglot ignores any constructs that may not be supported, cascading to any other declarations that may use or refer to the unsupported feature.

Ada2Proxy

Access types

Access to scalar types

Access to scalar types are not supported.

type Unsupported is access all Integer;

Access to subprograms

Access to subprogram types are not supported.

type Unsupported is access procedure;

Access to access

Only one level of access indirection is supported

type P is private;

type Supported is access all P;

type Unsupported is access all Supported;

Access to arrays

Thin pointers to arrays are not supported.

type Arr is array (Positive range <>) of Integer;

type Unsupported is access all Arr
with Size => Standard'Address_Size;

Anonymous type declarations

Declarations that use anonymous declarations cannot be bound.

type P is private;

procedure Unsupported (Acc : access P);

Array Types

Arrays of arrays

Arrays of arrays and arrays of access to arrays are not supported.

type Arr is array (Positive range <>) of Integer;

type Acc is access all Arr;

type Unsupported_1 is array (Positive range <>) of Arr (1 .. 10);

type Unsupported_2 is array (Positive range <>) of Acc;

Aspects

Arrays with the Component_Size aspect, or the Pack aspect, are not supported.

type Unsupported_1 is array (Positive range <>) of Integer
with Pack;

type Unsupported_2 is array (Positive range <>) of Integer
with Component_Size => 64;

Constrained arrays

Constrained arrays are not yet supported.

type Unsupported is array (1 .. 10) of Integer;

Enumeration index

Arrays indexed by enumeration types are not bindable.

type Enum is (A, B, C);

type Unsupported is array (Enum range <>) of Integer;

Classwide objects

Any access to a classwide object is not supported. Moreover, functions that return a classwide object cannot be bound either.

type T is tagged null record;

type Unsupported_T is access all T'Class;

function Unsupported return T'Class;

procedure Support (Obj: T'Class);

Controlled types

During the destruction of a C++ object, the vtable is also progressively destroyed. At the start of any destructor execution, it is no longer possible to downcast the object to its original type, meaning that calling any virtual function would not call the deepest override either.

In Ada → C++ bindings, when an Ada class wide copy of a controlled type occurs, the C++ object is also cloned, and owned by the copy. Upon scope exit, the C++ finalize override may be called on the object, then it will be destroyed by the Shadow_Data.Finalize function.

However, when a C++ object is destroyed, it will call the destructor of the child type first, until it reaches the Ada Free function, that will call finalize. Since all the previous destructors were called, it will not be able to call the original type’s finalize function. In order to avoid this inconsistency, overriding the subprograms of controlled types was disabled.

#include <iostream>

void ada_finalize_foo() {}

// Ada type
struct Foo {
    virtual ~Foo();

    virtual void finalize() { /** ... */ }
};

// User type
struct Bar : public Foo {
    void intialize() {
        some_ptr = new int;
    }
    void finalize() override{
        delete some_ptr;
    }

    int *some_ptr;
};

Foo::~Foo() {
    std::cout << dynamic_cast<Bar *>(this)
                 // Will **always** return nullptr:
                 // The destructor was called *after* `~Bar()` which also
                 // cleared its own vtable: at this point the object is no
                 // longer a `Bar`, even if it was initialized as one.
              << "\n";

    ada_finalize_foo();
    // When calling the ada Finalize subprogram, it will *never* call
    // `Bar::finalize` since the finalize function is called in `~Foo`'s
    // frame: as shown by the dynamic_cast above, the object is no longer a
    // `Bar`
    //
    // Bar::some_ptr will unexpectedly leak.
}

int main() {
    Bar b;
}

Discriminated types

Discriminated types are not yet supported.

type Unsupported (I: Integer) is null record;

Fixed point types

Fixed point types are not yet supported.

type Unsupported is delta 10.0 ** (-1) digits 3;

Generic declarations

Generic declarations cannot be bound and will be ignored.

package Example is
   generic
      type T is <>;
   package Unsupported is
   end Gen;
end Example;

Inheritable types

In Ada, tagged type primitives can have multiple controlling parameters, and can also have a controlling return type (dynamic dispatch based on return type).

type Root is tagged private;
function F(R1, R2: Root) return Root;

type Child is new Root with private;
overriding function F(R1, R2: Child) return Child;

Since there is no corresponding capability in most other programming languages, any tagged type with a primitive that makes use of such language features will be marked as final in the proxy.

class Root {
    // No shadow constructor
    Root();

    // Not virtual
    Root f(const Root &r1, const Root &r2);
};

If one of the tagged type’s primitives is not bindable, then the type will also be marked as final.

Integer types

Integer types that require more than 64 bits are not supported.

function Unsupported return Long_Long_Integer;

Interfaces

Interfaces are not supported.

type Unsupported is interface;

Limited Types

When creating a new object of a limited record type, a copy of all the parameters is done to initialize the components of the record. In the case where one of the component also has a limited type, it becomes impossible to perform the copy: Ada2Proxy is unable to add a constructor that would accept these parameters. Only a constructor that takes default values in consideration will be generated.

type Rec1 is limited record
   I : Integer := 1;
end record;

type Rec2 is limited record
   R : Rec1;
end record;
class Rec1 {
    Rec1();
    Rec1(int);
};

class Rec2 {
    Rec2();
    // Rec2(const Rec1&) is not defined.
};

Nested packages

Although child packages are supported, nested packages are not yet supported.

package Example is

   package Unsupported is
   end Unsupported;

end Example;

package Example.Supported is
end Example.Supported;

Wide characters

The Wide_Character and Wide_Wide_Character types are not supported.

type Unsupported_Str is arrays (Positive range <>) of Wide_Character;

Proxy2Cpp

C++ object construction

The constructor of the parent type is called first. It will initialize its own vtable only.

It is not possible to know at this point if the ctor was called by a child or not, since the child’s vtable is not initialized yet, making it not possible to use RTTI to select between the alloc and shadow_alloc ctor automatically.

class B;
class A {
public:
    A() {
        // Will always be true
        if (dynamic_cast<B*>(this) == nullptr)
            std::cout << "Not a `B`" << "\n";
    }
};

class B : public A { };

int main() {
    B b;
}

Instead, the shadow_alloc constructors are generated with an additional argument, that is the this argument passed to the shadow object to perform dynamic dispatch back to C++.

class Foo {
protected:
    // Shadow alloc ctor
    Foo(int a, Foo *self);

public:
    Foo(int a);
}

In order for the bound library to make use of the overriding function of the inheriting type, it becomes necessary to call the constructor with the extra parameter:

class Bar : public Foo {
    // Correct: will construct an object of shadow type.
    Bar(int a) : Foo(a, this) { }

    // Incorrect: will construct a simple `Foo` object in the library.
    Bar(int a) : Foo(a) { }
}

Polymorphic copies

Some bound languages may be able to perform polymorphic copies (e.g Ada). When they occur on Shadow types, it is necessary to also make a clone of the C++ object to which they have a reference. C++ does not provide a way to perform polymorphic copies out of the box, so when inheriting a bound type, it is necessary to manually provide a way to perform such copies through the overridable internal_clone function member:

type Root is tagged private;
class Child : public Root {
protected:
    Root *internal_clone(void *data) {
        return new Child(*this, data);
    }

    Child(const Child &other, void *data) : Root(data) { }
}