• Places
    • Home
    • Graphs
    • Prefixes
  • Admin
    • Users
    • Settings
    • Plugins
    • Statistics
  • CPACK
    • Home
    • List packs
    • Submit pack
  • Repository
    • Load local file
    • Load from HTTP
    • Load from library
    • Remove triples
    • Clear repository
  • Query
    • YASGUI SPARQL Editor
    • Simple Form
    • SWISH Prolog shell
  • Help
    • Documentation
    • Tutorial
    • Roadmap
    • HTTP Services
  • Login

1.8 Rationale for changes from version 1
All Application Manual Name SummaryHelp

  • Documentation
    • Reference manual
    • Packages
      • A C++ interface to SWI-Prolog
        • A C++ interface to SWI-Prolog
          • Rationale for changes from version 1
            • Implicit constructors and conversion operators
            • Strings

1.8.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 a bool 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 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 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.

ClioPatria (version V3.1.1-51-ga0b30a5)