.. _flintxx: **flintxx** -- C++ Wrapper =============================================================================== Introduction ------------------------------------------------------------------------------- Flint provides a C++ wrapper which makes extensive use of metaprogramming. It is currently not maintained for the whole of Flint due to lack of a C++ developer willing to step up and maintain it. Therefore it is provided as-is, though we do try to do sufficient maintenance to keep the `currently wrapped functions `_ compiling. In this section we describe how to use ``flintxx`` the Flint C++ wrapper. We begin with a simple example: .. code-block:: C #include "fmpzxx.h" using namespace flint; fmpzxx x, y; x = 7u; y = x*x; std::cout << x << "^2 = " << y << std::endl; As can be seen, if a FLINT C interface is called ``foo``and resides in ``foo.h``, then the C++ version is called ``fooxx`` and resides in ``fooxx.h``. All flintxx classes live inside ``namespace flint``. Functions which operate on wrapper classes are usually implemented as overloaded stand-alone functions, with the type prefix dropped. For example, a call to ``flint::gcd(f1, f2)`` yields an expression template evaluating via ``fmpz_gcd``, provided ``f1`` and ``f2`` evaluate to instances of ``fmpzxx``. Sometimes we felt that dropping the type prefix would yield incomprehensible names, as for example in ``fmpq_next_minimal``, or ``fmpq_reconstruct``. In these cases the type prefix is swapped for the flintxx equivalent, so the flintxx version would be called ``fmpqxx_reconstruct``. In this situation, usually the same functionality is also exposed as a (possibly static) member function, and this is the preferred way of accessing the functionality. Thus one should write ``fmpqxx::reconstruct(a, m)`` or ``fmpqxx(0, 1u).next_minimal()``. Expression templates ------------------------------------------------------------------------------- The implementation of flintxx tries very hard not to incur any overhead over using the native C interface. For this reason, we use ``expression templates`` for lazily evaluating expressions. This allows us to avoid creating excessively many temporaries, for example. This means that even if ``x`` and ``y`` are of type ``fmpzxx``, then ``x + y`` will not be of type ``fmpzxx``. Instead it will be an object which for most purposes behaves just like ``fmpzxx``, but really only expresses the fact that it represents the sum of ``x`` and ``y``. This distinction almost never matters, since expression templates are evaluated automatically in most cases. Thus ``cout << x + y`` or ``x + y == 7`` will work just as one might expect. There are ways to request explicit evaluation of an expression template, most notably ``(x + y).evaluate()`` and ``fmpzxx(x + y)``. One caveat of the expression template approach is that compiler error messages can be long and hard to understand. In flintxx we strive to type-check template parameters as early as possible in order to keep error messages short and close to the actual user error. Excessively long error messages are often indicative of a bug in flintxx. Tuples ------------------------------------------------------------------------------- Many FLINT functions naturally return two or more arguments. A typical example is ``divrem``. The underlying C function is ``void fmpz_poly_divrem(fmpz_poly_t Q, fmpz_poly_t R, const fmpz_poly_t A, const fmpz_poly_t B)``. Mapping this directly to C++ would yield something like ``void divrem(fmpz_polyxx& Q, fmpz_polyxx& R, const fmpz_polyxx& A, const fmpz_polyxx& B)}``. While functional, this is not particularly nice; the syntax ``divrem(Q, R, A, B)``, where the first two arguments are modified, is just very reminiscent of C. We prefer an expression closer to the python analogue ``(Q, R) = divrem(A, B)``. For this purpose, flintxx uses lazy tuples and the following is a valid flintxx expression: ``ltupleref(Q, R) = divrem(A, B)``. For the purpose of this documentation, ltuple types are denoted as follows: ``Ltuple``. Thus, ``divrem`` would return an object of type ``Ltuple``. The user should never try to construct such types by hand; instead use the function ``ltupleref`` (and perhaps occasionally ``ltuple``; both documented later). One thing to observe is that ltuples are typed fairly weakly. Thus assignments and equality comparisons can be performed as long as both sides have the same length, and the operation can be performed on all components (whether or not the component types match). Another interesting feature of ltuples is the type ``flint::detail::IGNORED_TYPE``. In an ltuple assignment, where the left hand side contains a reference to this type, the relevant entry is just discarded. Since the ``ltuple.h`` header automatically creates a static instance ``_`` of this type, in the following listing, the lines marked (1) and (2) have the same effect (but the second is potentially more efficient). .. code-block:: C #include "fmpz_polyxx.h" using namespace flint; fmpz_polyxx f, g; fmpz_polyxx R; ltupleref(_, R) = divrem(f, g); // (1) R = f % g; // (2) Note finally that using ``ltuple`` intermediates often results in more copies than necessary. For example the expression ``ltupleref(num, _) = divrem(a, b)`` assigns the quotient to ``num``, creating just a temporary ``fmpzxx`` to hold the remainder. In contrast, ``num = divrem(a, b).get<0>()`` creates two temporary instances of ``fmpzxx``. Reference types ------------------------------------------------------------------------------- One subtlety in wrapping a C library is that references do not work as easily as one might expect. For example, consider the class ``fmpqxx``, wrapping ``fmpq_t``, i.e. rational numbers. As such, an instance of ``fmpqxx`` has a numerator and denominator. In C, these are accessible via macros ``fmpq_numref`` and ``fmpq_denref``, which yield ``fmpz*``, which can be used essentially interchangeably with ``fmpz_t``. In particular, any library function which operates on ``fmpz_t`` can operate on the numerator or denominator of an ``fmpq_t``. In C++, we would like to have a member functions ``den` and ``num`` which return an object of type ``fmpzxx&`` (i.e. a reference to ``fmpzxx``). However, this is not possible, since ``fmpqxx`` is not implemented as a pair of ``fmpzxx``, and instead simply contains an ``fmpq_t``. For this reason, for every C interface ``foo``, flintxx provides two additional types, called ``fooxx_ref`` and ``fooxx_srcref``, acting as replacements for ``fooxx&`` and ``const foox&``, respectively, in situations where no underlying C++ object exists. Instances of ``fooxx_ref`` or ``fooxx_srcref`` behave exactly like instances of ``fooxx``. In fact, the user should never notice a difference. Any flintxx operation or expression which works on objects of type ``foo`` also works on objects of type ``fooxx_ref`` and ``fooxx_srcref``. Moreover, instances of ``foo`` can be converted implicitly to ``fooxx_ref`` or ``fooxx_srcref``, and ``fooxx_ref`` can be converted implicitly to ``fooxx_srcref``. It is also possible to explicitly convert reference types ``fooxx_*ref`` to ``fooxx`` (since this entails copying, we provide no implicit conversion). In summary, the class ``fooxx_ref`` behaves like a reference to an object of type ``fooxx``. As such it can be used both as a right hand side and as a left hand side, just like ``fooxx``. The class ``fooxx_srcref`` behaves like a reference to a constant object of type ``fooxx``, and so cannot be used as a left hand side. These objects are created by flintxx automatically,`for example upon calling ``fmpqxx::num()``. Unified coefficient access ------------------------------------------------------------------------------- Consider again the ``x.num()`` method of ``fmpqxx``. In various situations, this can have different return types. Namely, if ``x`` is a writable expression, then ``x.num()`` returns an ``fmpzxx_ref``. In particular the return value behaves just like ``fmpzxx``, no evaluation is necessary to obtain it, there are no copies, and it is possible to change the return value (and thus change ``x``). If on the other hand ``x`` is a readonly immediate, then the return value of ``x.num()`` has type ``fmpzxx_srcref``. This again behaves just like ``fmpzxx`` and no evaluations or copies are necessary, but this time it is not possible to change the return value (and so it is not possible to change ``x``, either). Finally, if ``x`` is a lazy expression, then the return value is actually a lazy expression template. Thus to obtain the "actual" value of ``x.num()``, evaluations are necessary, and potentially so are copies. Thus in any case the return value behaves just like ``fmpqxx``, but apart from that the behaviour of ``x.num()`` varies quite drastically in the different situations. We call this "unified coefficient access" (the coefficients of a ``fmpqxx`` being ``num(), den()``), and the same behaviour occurs in many other flintxx types, e.g. in ``fmpz_polyxx.coeff()``, etc. Type conversion ------------------------------------------------------------------------------- As a rule, flintxx does not perform automatic type conversions (except when related to the promotion to reference types, c/f earlier discussion). In expression templates, operands can be automatically promoted if the underlying C interface provides this facility. Beyond that, types have to be converted explicitly. There are two ways of doing this. The preferred one is using static constructor functions. Typical examples are ``fmpz_polyxx::from_ground(fmpzarg)`` and ``nmod_polyxx::reduce(mplimbarg, nmodctxarg)``. The former takes an (expression template evaluating to) ``fmpzxx`` and returns an ``fmpz_polyxx`` representing the constant polynomial with value the ``fmpzxx``. The latter takes an argument of type ``mp_limb_t`` and one of type ``nmodxx_ctx_srcref`` (essentially a word-sized modulus) and returns an ``nmod_polyxx`` representing the constant polynomial obtained by reducing ``mplimbarg``. The general format for this is ``totype::constructorname(arg1, arg2, ...)``. We prefer this because it makes explicit the type that is being converted to, and the way the arguments are to be interpreted. This format only works if the target type is part of flintxx. In other cases, we use a ``.to()`` syntax, as in ``fmpzexpr.to()``. Input and output ------------------------------------------------------------------------------- In C++ it is customary to provide input and output via iostreams, and overloading the operators ``<<`` and ``>>``. When wrapping a C library which works on the ``FILE`` interface, this is rather hard to accomplish. For this reason, flintxx only provides streaming output (i.e. ``<<``), and only when there is a ``to_string`` method. Unfortunately this applies to only a small subset of the FLINT types. For output in other cases, and input in all cases, we provide C-like functions. Namely, the functions ``print``, ``print_pretty``, ``read`` and ``read_pretty`` can be used similarly to the C ``flint_printf`` and ``scanf``. For example, ``print(x)`` where ``x`` is an ``fmpz`` has the same effect as ``std::cout << x``. Inheritance and flintxx ------------------------------------------------------------------------------- The flintxx classes are not designed for inheritance. If you want to modify behaviour, you should wrap flintxx types into your own classes (extension by aggregation, not inheritance). Notation and conventions in flintxx documentation ------------------------------------------------------------------------------- As explained above, the flintxx classes and functions perform quite a number of operations which should be invisible to the user. Some template types implement methods which only make sense for some template arguments, etc. For example, every expression template built from ``fmpq_polyxx`` (polynomials with rational coefficients) has a method ``set_coeff``. However, this method only makes sense for objects of type ``fmpq_polyxx`` or ``fmpq_polyxx_ref`` (calling it on other types will result in a compilation error), and its existence in objects of other types should be considered an implementation detail. In what follows, we document a "virtual" set of classes and functions, which explain how the user should expect its objects to behave, and which we guarantee to maintain. Other interfaces should be considered implementation details and subject to change. Consider the interface ``fmpzxx``, and more concretely an instance ``a``. As in the above discussion, we see that from ``a`` we can build a lot of different objects: expression templates like ``a+a``, constant objects like ``const fmpzxx& b = a;``, reference objects like ``fmpzxx_ref c(a)``, etc. These by nature behave somewhat differently. For our purposes, we classify types into "targets" (things which can be assigned to), "sources" (things which contain actual computed data, or references thereto, as opposed to lazy expression templates) and "expressions" (sources or expression templates). Note that every target is a source, and every source is an expression. We denote any type which can act as a target for ``fmpzxx`` as ``Fmpz_target`` (note the initial capital letter!), any ``fmpzxx`` source as ``Fmpz_source`` and any ``fmpzxx`` expression as ``Fmpz_expr``. Such made up type names (always with initial capital letter) are referred to as "virtual types" in the documentation. These are used for all flint classes (e.g. ``Fmpq_expr`` or ``Fmpz_polyxx_src``). When using virtual types, we will suppress reference notation. No flintxx types are ever copied automatically, unless the documentation explicitly says so. This is a general philosophy of flintxx: the library does as many things automatically as it can, without introducing additional calls to underlying Flint C functions. So for example, it is not possible to implicitly convert ``int`` to ``fmpzxx`` (since doing so requires a C call). Of course explicit conversions (or assignments) work completely fine. It is also often the case that flintxx functions are conditionally enabled templates. A notation such as ``void foo(T:is_signed_integer)`` denotes a template function which is enabled whenever the template parameter ``T`` satisfies the type trait ``is_signed_integer``. These type traits should be self-explanatory. In what follows, we will never document copy constructors, or implicit conversion constructors pertaining to reference types. We will also not document assignment operators for expressions of the same type. Thus if ``x`` is an ``fmpzxx`` and ``y`` is an ``fmpqxx``, then ``x = x`` and ``y = x`` are both valid, but only the second assignment operator is documented explicitly. Most flintxx functions and methods wrap underlying C functions in a way which is evident from the signature of the flintxx function/method. If this is the case, no further documentation is provided. For example, the function ``double dlog(Fmpz_expr x)`` simply wraps ``double fmpz_dlog(const fmpz_t)``. As is evident from the return type, ``dlog`` immediately evaluates its argument, and then computes the logarithm. In contrast, a function like ``Fmpz_expr gcd(Fmpz_expr, Fmpz_expr)`` returns a lazily evaluated expression template and wraps ``void fmpz_gcd(fmpz_t, const fmpz_t, const fmpz_t)``. In case a Flint C function has more than one return value in the form of arguments passed in by reference, the C++ wrapper returns an ``ltuple``. In this case, the order of the ``ltuple`` arguments is the same as the order of the function arguments; so for example ``ltupleref(Q, R) = divrem(A, B)`` has the same effect as ``fmpz_poly_divrem(q, r, a, b)``, provided ``Q, R, A, B`` are ``fmpz_polyxx`` and ``q, r, a, b`` are the underlying ``fmpz_poly_t``. If such a convention is followed, the documentation below may not further explain anything. In all other cases, further explanation is provided (this applies in particular if the C function has return type different from ``void``). Global functions or member functions? ------------------------------------------------------------------------------- Often it is not clear if functionality is exposed as a global function, such as ``gcd(a, b)``, or as a member function, such as ``a.gcd(b)``. In flintxx, we strive to make both available when feasible. In the documentation, the global versions are documented in detail (explaining the allowed types etc), whereas the member function versions are summarised more briefly under e.g. ``Fmpz_expr::unary operation() const``, ``Fmpz_expr::binary operation(??) const`` etc.