2 A C++ interface to SWI-Prolog (Version 2)
AllApplicationManualNameSummaryHelp

  • Documentation
    • Reference manual
    • Packages
      • A C++ interface to SWI-Prolog
        • A C++ interface to SWI-Prolog (Version 2)
          • Summary of changes between Versions 1 and 2
          • Introduction (version 2)
          • The life of a PREDICATE (version 2)
          • Overview (version 2)
          • Examples (version 2)
          • Rational for changes from version 1 (version 2)
            • Implicit constructors and conversion operators
            • Strings
          • Porting from version 1 to version 2
          • The class PlFail (version 2)
          • The class PlTerm (version 2)
          • The class PlTermv (version 2)
          • The class PlAtom - Supporting Prolog constants (version 2)
          • Unification and foreign frames (version 2)
          • The class PlRegister (version 2)
          • The class PlQuery (version 2)
          • The PREDICATE and PREDICATE_NONDET macros (version 2)
          • Exceptions (version 2)
          • Embedded applications (version 2)
          • Considerations (version 2)
          • Conclusions (version 2)

2.6 Rational for changes from version 1 (version 2)

2.6.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 " << A1.as_string() << endl;
  return true;
}

PREDICATE(add, 3)
{ return A3 = (long)A1 + (long)A2;
}

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 C-style of casts is deprecated in C++, so the expression (char *)A1 becomes the more verbose static_cast<std::string>(A1), which is longer than A1.as_string(). Also, the string casts don't allow for specifying encoding.
  • The implicit constructors and conversion operators 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");

  • 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 ctypebool. 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 a bool and they can be wrapped inside a PlCheck() to raise an exception on unification failure.

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, some 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.

2.6.2 Strings

The version API often used char* for both setting and setting string values. This is not a problem for setting (although encodings can be an issue), but can introduce subtle bugs in the lifetimes of pointers if the buffer stack isn't used properly. The buffer stack is abstracted into PlStringBuffers, but it would be preferable to avoid its use altogether. C++, unlike C, has a standard string that allows easily keeping a copy rather than dealing with a pointer that might become invalid. (Also, C++ strings can contain null characters.)

C++ has default conversion operators from char* to std::string, so some of the API support only std::string, even though this can cause a small inefficiency. If this proves to be a problem, additional overloaded functions and methods can be provided in future (note that some compilers have optimizations that reduce the overheads of using std::string); but for performance-critical code, the C functions can still be used.

There still remains the problems of Unicode and encodings. std::wstring is one way of dealing with this. And for interfaces that use std::string, an encoding can be specified.12As of 2022-11, this had only been partially implemented. Some of the details for this - such as the default encoding - may change slightly in the future.