Embedded cpp exceptions

That is the example from @peter.ludemann’s cpp2 pack, I used the code from SWI-Prolog -- The class PlTerm_tail and added a typo that should raise an exception.

int
main(int argc, char *argv[])
{ PlEngine e(argv[0]);
  PlTermv av(1);
  PlTerm_tail l(av[0]);

  for(int i=0; i<argc; i++)
    PlCheckFail(l.append(PlTerm_string(argv[i])));
  PlCheckFail(l.close());

  PlQuery q("writelnnnn", av); // Note the typo
  try
  { return q.next_solution() ? 0 : 1;
  } catch ( PlException &ex )
  { std::cerr << ex.what() << std::endl;
    return 1;
  }
}

If I compile it using swipl-ld example.cpp and then ./a.out 1 2 3, I don’t get an exception.

I’ll try this out and either tell you why it’s Working As Designed(*) or I’ll fix the bug/documentation. I’m in the middle of something else, so unless this is urgent, I’ll probably take a few days.

(*) that is: “Broken As Designed” or “BAD”

1 Like

Sorry I took so long to respond.
PlQuery::next_solution() doesn’t throw an exception – because, as documented, you must use PL_exception(0) to determine whether a false result means failure or an exception. Alternatively, you can replace q.next_solution() with PlWrap<int>(q.next_solution()) to do the check and throw an exception if warranted.
The sample code in main.cpp uses PlWrap<int>(...) but I failed to update the documentation. I’ll do a quick fix to the documentation; I may not be able to properly fix the documentation before the end of the month.

This isn’t an ideal API but it’s necessitated by the possibility of the flag PL_Q_EXT_STATUS to PL_open_query() which gives results other than false or true (0 or 1 )and PlQuery::next_solution() doesn’t know that which flag was used. Perhaps this should change … I’ll review a PR if you make one (pl2cpp.doc needs to be updated also).

I actually switched to PL_Q_EXT_STATUS, as you suggest, and check PL_exception(0) if an exception occurred. Thanks!

It may take a bit of time until the PR arrives.

I don’t know whether it would help, but the flags to create the query are stored, so we could add some call to retrieve them.

I thought about this (the constructor for PlQuery could make a copy of the flags if they aren’t available via a PL_*() function) but that would give different semantics for PlQuery::next_solution(), depending on the flags (that is, it would throw an exception unless the flags are PL_Q_EXT_STATUS).

Perhaps a better solution would be to have 2 versions of PlQuery::next_solution(), (e.g., next_solution_check(), next_solution_ext) and check at runtime that the flag is as expected. PlCheckFail() is the standard way to throw an exception if next_solution() fails.

A function for getting the flag would be a good idea, for debugging if nothing else.

On the other hand, why no go the way it also handled in the JavaScript and Python interfaces: the high level calls PL_open_query() with the proper flags that allows us to use PL_next_solution() such that it fits the language? Ok, someone can call the low level C PL_open_query() with different flags and then hope the high level works, which may not be the case. I think that can simply be documented. Either use the C API all the way to manage the lifetime of a query or use the C++. Don’t mix them.

I’m ok with an API to extract the flag, but only if it really improves the interface. Otherwise it merely bloats the API. For now, I’m not convinced.

1 Like

I think that the solution is for PlQuery::next_solution() to throw a C++ exception if there was a Prolog exception, otherwise return false for failure (and true for success) unless the PlQuery was constructed with flag PL_Q_EXT_STATUS, in which case no C++ exception is thrown and other return values are possible. I don’t see an easy way of enforcing the distinction between the two possible results, either using type declarations or checking at run-time, so we’ll just have to rely on documentation.

AFAIK, it doesn’t hurt to call PL_exception(0) twice, so PlWrap<int>(q.next_solution()) doesn’t hurt even if PlQuery::next_solution() checks for an exception, except for the overhead. So, we can leave existing code as-is (except for the examples) and document the situation.

In the case of PL_Q_EXT_STATUS, PlWrap<int>(...) will neverdetect a Prolog exception because the return code is -1 (PL_S_EXCEPTION) and it won’t call PL_exception(); that is, there’s no ambiguity between failure and a Prolog exception.

There’s no need to use the PL_open_query() flags when checking the result of PL_next_solution() – in the case of PL_Q_EXT_STATUS, a zero will always mean failure, so the check for PL_exception(0) will always fail. This is a tiny bit of overhead, so probably not worth trying to optimize. (But we could have a variant of PlQuery::next_solution() if this bothers anyone)

(Maybe PlWrap<...>() should be called PlPrologExceptionToCppException().)