2.7.1 Implicit constructors and conversion operators
The original version of the C++ interface heavily used implicit constructors and conversion operators. This allowed, for example:
PREDICATE(hello, 1) { cout << "Hello " << (char *)A1 << endl; // Deprecated return true; } PREDICATE(add, 3) { return A3 = (long)A1 + (long)A2; // Deprecated }
Version 2 is a bit more verbose:
PREDICATE(hello, 1) { cout << "Hello " << A1.as_string() << endl; return true; } PREDICATE(add, 3) { return A3.unify_int(A1.as_long() + A2.as_long()); }
There are a few reasons for this:
- The implicit constructors and conversion operators, combined with the C++ conversion rules for integers and floats, could sometimes lead to subtle bugs that were difficult to find -- in one case, a typo resulted in terms being unified with floating point values when the code intended them to be atoms. This was mainly because the underlying C types for terms, atoms, etc. are unsigned integers, leading to confusion between numeric values and Prolog terms and atoms.
- The overloaded assignment operator for unification changed the usual
C++ semantics for assignments from returning a reference to the
left-hand-side to returning a
bool
. In addition, the result of unification should always be checked (e.g., an “always succeed” unification could fail due to an out-of-memory error); the unify_XXX() methods return abool
and they can be wrapped inside a PlCheckFail() to raise an exception on unification failure. - The C-style of casts is deprecated in C++, so the expression
(char*)A1
becomes the more verbosestatic_cast<std::string>(A1)
, which is longer thanA1.as_string()
. Also, the string casts don't allow for specifying encoding. - The implicit constructors and conversion operators were attractive
because they allowed directly calling the foreign language interface
functions, for example:
PlTerm t; Pl_put_atom_chars(t, "someName");
whereas this is now required:
PlTerm t; Pl_put_atom_chars(t.as_term_t(), "someName");
However, this is mostly avoided by methods and constructors that wrap the foreign language functions:
PlTerm_atom t("someName");
or
auto t = PlTerm_atom("someName");
Additionally, there are now wrappers for most of the PL_*() functions that check the error return and throw a C++ exception as appropriate.
Over time, it is expected that some of these restrictions will be eased, to allow a more compact coding style that was the intent of the original API. However, too much use of overloaded methods/constructors, implicit conversions and constructors can result in code that's difficult to understand, so a balance needs to be struck between compactness of code and understandability.
For backwards compatibility, much of the version 1 interface is still available (except for the implicit constructors and operators), but marked as “deprecated” ; code that depends on the parts that have been removed can be easily changed to use the new interface.