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.
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 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.
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().)