.. _proxy2cpp: ********* Proxy2Cpp ********* Using the tool -------------- The Proxy2cpp tool is invoked as a subcommand of the Polyglot program. It must be provided with at least a proxy file and an output path: .. code:: sh $> polyglot proxy2cpp -o {} * ``--help`` Display Help. * ``-q`` be quiet. * ``-v`` verbose output. * ``-o|--output=`` path to the directory in which the C++ interface should be generated. Directories along the path are created if they do not exist yet. * ``--header-file=`` Prepend the content of the given file to all sources generated by Polyglot. Modules ------- Modules in the JSON Proxy are translated to namespaces and are each in a separate file. Nested modules become nested namespaces. .. list-table:: :header-rows: 1 * - Ada declarations - Generated C++ headers * - .. code:: ada -- example.ads package Example is procedure Foo; end Example; - .. code:: cpp // example.h namespace example { void Foo(); } // namespace example * - .. code:: ada -- example-child.ads package Example.Child is procedure Bar; end Example.Child; - .. code:: cpp // example-child.h namespace example { namespace child { void Bar(); } // namespace child } // namespace example Functions --------- Function placement ~~~~~~~~~~~~~~~~~~ By default, functions will be placed in their parent namespace. Functions with roles become member functions to the type of the role. .. list-table:: :header-rows: 1 * - proxy.json - (proxy2cpp →) C++ header * - .. code:: json { "kind": "function", "name": {"names": ["n1", "f"]} }, { "kind": "class", "name": {"names": ["n1", "c"]} }, { "kind": "function", "name": {"names": ["n1", "fm"]}, "role": { "kind": "method", "type": {"kind": "typename", "name": {"names": ["n1", "c"]}} } } - .. code:: cpp namespace n1 { void f(); class C { void fm(); }; } // namespace n1 Operators ~~~~~~~~~ Functions from a proxy that have a name matching a placeholder name will be translated to an operator overload. .. list-table:: :header-rows: 1 * - Ada declaration - (ada2proxy →) proxy.json - (proxy2cpp →) C++ header * - .. code:: ada type T is private function "+"(I: Long_Integer; V: T) return Integer; - .. code:: json { "kind": "function", "name": {"names": ["operator_plus"]} } - .. code:: cpp int operator+(long i, T v); Class types ----------- Final types ~~~~~~~~~~~ Classes marked ``final`` in the proxy should not be inherited. They lack the support for dynamic dispatch in the bound library. Currently, nothing prevents inheriting from such types. However, in the future, we will add a mechanism to prevent inheritance, and as such incorrect behaviours when that would happen. Function members ~~~~~~~~~~~~~~~~ As mentioned previously, functions with a role will be function members of the class. The first parameter has to be of the same type as the role's. The latter becomes the implicit ``this`` value. If the first argument type in the proxy is constant, then the the function member will be marked as ``const``. Inheritance ~~~~~~~~~~~ Types with a shadow constructor can be inherited. It is mandatory to use the shadow constructor in order to enable overriding non static function members from bound types. The shadow constructor can be distinguished by its last argument: when constructing the shadow object of a type ``T``, its constructor will expect a ``T*``. This argument expects the ``this`` value. .. code:: ada package Example is type Foo is tagged null record; end Example; .. code:: cpp // example.h class Foo { // Normal constructor: creates an `Example.Foo` ada object. Foo(); // Shadow constructor Foo(Foo *); }; // main.cpp class Bar : public example::Foo { // valid: creates a shadow object Bar() : example::Foo(this) {} // Invalid: the input library will not create a shadow object, // dispatching will not work Bar() : example::Foo() {} } .. note:: This is necessary due to C++ initializing the vtable for a given type only at the time that is it being constructed. When executing ``example::Foo``\ 's constructor, the vtable of ``Bar`` is not yet initialized, meaning that it cannot be aware that the object being constructed actually inherits from ``example::Foo``. Overriding virtual functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Virtual functions from bound types can be overriden. This allows inside the bound libraries to dynamically dispatch back to overrides. .. list-table:: :header-rows: 1 * - Ada declaration - C++ code using and overriding the generated bindings * - .. code:: ada package Example is type Foo is tagged private; procedure Hello (F : Foo) is begin Put_Line ("Hello from Ada"); end Hello; procedure Call_Hello (F : Foo'Class) is begin F.Hello; -- Dispatching call end Call_Hello; end Example; - .. code:: cpp // example.h namespace example { class Foo { public: Foo(); virtual void hello() const; }; void call_hello(const Foo&); } // namespace example // main.cpp class Bar : example::Foo { Bar() : example::Foo(this) {} void hello() const override { std::cout << "Hello from C++\n"; } }; int main() { example::Foo foo; Bar bar; example::call_hello(foo); // Hello from Ada; example::call_hello(bar); // Hello from C++; } Uncopyable classes (copy elision (C++17)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C++ only supports guaranteed copy elision since C++17. However, there may be times where input languages support returning uncopyable values through similar concepts to copy elision. In order to avoid errors with invalid code generation, when these values are returned before with standards prior to C++17, they will be returned though a pointer. These values, since cloned before by the proxy before returning, will be owned by the user. Polyglot pointers ----------------- Pointer types are represented using a ref-counted smart pointer (``polyglot_ptr``) in order to avoid manual memory management. The smart pointer holds information on the ownership of the underlying pointer. Creating a pointer from an existing object sets the owner to ``STATIC``. It implies that the memory should not be freed when the pointer goes out of scope, and the owner cannot be changed. When passing a pointer to a function, the function expects a minimum level of ownership. This is to make sure that after calling a function that may escape the pointer, the latter should not be freed inadvertently which could leave a dangling pointer. .. code:: ada package body Example is type Rec is record I: Integer; end record; type Rec_A is access all Rec; Acc : Rec_A := null; procedure Set (New_Acc: Rec_A) is begin Acc := New_Acc; end Add; procedure Increment is begin Acc.all := Acc.all + 1; end Add; end Example; .. code:: cpp // example.h namespace example { class Rec { Rec(int); int &get_i(); void set_i(int i); }; void set(polyglot::polyglot_ptr new_acc); void inc(); } // namespace example // main.cpp int main() { { polyglot::polyglot_ptr ptr(new Rec(4)); // `Example.Set` only accepts the default minimum ownership: LIBRARY ptr.set_owner(polyglot::memory_owner::LIBRARY); example::set(ptr); example::inc(); // The managed C++ object of `ptr` is freed, but its underlying Ada // data is not because it is owned by the library. } // We can keep calling `Example.Inc` example::inc(); example::inc(); } .. note:: For a concrete example of using pointers, checkout the "ex2" example References and view types ------------------------- Returning non-scalar references is not possible, so instead, view types are generated. Similarly to the ``std::vector::reference`` specialization, their goal is to provide a value that can be used as a reference of their underlying type. As a comparison, a pointer with a ``static`` ownership would result in a similar result, but the outcome would not reflect the behavior of the interface as well. Destruction of such view objects do not cause the underlying data to be freed. References to Scalar type are still translated to usual C++ references. .. code:: ada package Example type P is private; type Rec is record Comp: P; end record; procedure Foo (Val: in out P); end Example; .. code:: cpp // example.h namespace example { class P {}; class Rec { P::view get_comp(); void set_comp(const P&); }; void foo(P &); } // namespace example // main.cpp int main() { Rec r; P::view ref = r.get_comp(); example::foo(ref); // View types can automatically convert to their reference counterpart example::foo(r.get_comp()); P &ref_invalid = r.get_comp(); // Invalid: the reference outlives the view object. example::foo(ref_invalid); // Undefined behaviour: the view object is no longer valid. The value of the reference is unknown. } Exceptions ---------- Exceptions are bound as classes that inherit the ``std::exception`` type. Polyglot guarantees the consistency of C++ exception types at run time. .. list-table:: :header-rows: 1 * - Ada declaration - C++ code using the generated bindings * - .. code:: ada package Example is Exc : exception; procedure Raise_Exc is begin raise Exc with "message"; end Raise_Exc; end Example; - .. code:: cpp // example.h namespace example { class Exc : public polyglot::ada::exceptions::AdaException { public: Exc(); Exc(const polyglot::ada::strings::polyglot_string &); }; void raise_exc(); } // namespace example // main.cpp #include "example.h" int main() { try { example::raise_exc(); } catch (const example::Exc& ex) { std::cout << ex.what() << '\n'; } } .. note:: For a concrete example of using bound exceptions, checkout the "ex3" example