C++ interface inline or not?

In a related issue (maybe I should start a new thread?), I’m becoming concerned that SWI-cpp2.h will result in executable bloat, because every function is being marked as inline (there is no SWI-cpp2.cpp that contains the implementation of any of the functions or methods).

The reason for using inline everywhere is that it avoids the problems of different compilers’ name mangling – otherwise, we’d need multiple libswpl.sos – one for each possible compiler.

But I’m wondering if there’s an alternative – the user could build a SWI-cpp2.o (or SWI-cpp2.cpp.o) for their particular compiler and “statically” link that with their code. I don’t know enough about how cmake works to know how to package this, but maybe it wouldn’t be necessary because the “pack” install uses make (although there might be some make/cmake interactions that I don’t understand).

For now I prefer the portability and simplicity. With a library, we have to deal with different ABIs as well as different versions, making it all pretty complex. And, luckily most interfaces are rather small. The two tests you wrote do seem to indicate that the C++ binary is a lot longer (>10 times) than the C one. I haven’t checked in how far the tested functionality is comparable.

My proposal is to make a file SWI-cpp2.cpp, which contains all the non-inline code. This would not be compiled as part of libswpl.so – instead, it would be compiled (as a separate .o) as part of whatever foreign predicate code uses SWI-cpp2.h. This avoids the problem of different ABIs (everything is built with the same compiler) and also prevents a code size explosion from everything being marked inline.

It’s a bit of work to do the splitting-up, so I’d like agreement before proceeding. (And there’s always a chance that it won’t work – C++ compilers and the language spec can have some unpleasant subtleties; but I presume that a package can have multiple .o files, which are combined into a single .so for the foreign predicates code; and I presume there’s no problem with the same extern functions appearing in multiple .so files.)

Right now, the following is the biggest “inline” function (I’ve removed the comments). All the code inside the if (!rc) could be done with a non-inline function, but currently it’s done for every call to a PL_*() function that has a return code (which is most of them).

template<typename C_t> [[nodiscard]] C_t
PlWrap(C_t rc, qid_t qid)
{ if ( !rc )
  { PlTerm_term_t ex(PL_exception(qid));
    if ( ex.not_null() )
    { static PlFunctor FUNCTOR_error_2("error", 2);
      static PlFunctor FUNCTOR_resource_error_1("resource_error", 1);
      if ( ex.is_functor(FUNCTOR_error_2) &&
           ex[1].is_functor(FUNCTOR_resource_error_1) )
        throw PlFail();
      const PlException ex2(ex);
      PL_clear_exception();
      throw ex2;
    }
  }
  return rc;
}

It seems that the compiler isn’t required to obey an inline directive (and if it doesn’t inline, it also won’t produce the multiple-definition errors): Inline (Using the GNU Compiler Collection (GCC))

However, we need at least 3 ways of specifying “you don’t need to inline this” (g++, clan, plus whatever MacOS uses; and then there’s Windows), possibly over multiple versions of the compilers. So, I’d prefer a straightforward approach that doesn’t get into compiler-specific details. I think that what I’m proposing is simple for people to use and avoids the ABI-incompatibility problems.

I still don’t really see the point of it, but there seems to be a simple in between: split and use a macro that allows defining how the functions of SWI-cpp2.cpp are defined, which can be one of inline, static or just global such that you can create a library from it. People can use it as they like.

Removes generated code bloat and makes compilation faster.
And it seems to have exposed some other issues with order of constructors … more fun times for me with gdb. :frowning:

This will be easy to add if needed. For now, I’m trying to reduce the complications as much as possible.
The easiest way of using my current code is by adding #include <SWI-cpp2.cpp> in the foreign predicate’s source file (which is what I did to swipl-win/main.cpp), or by adding SWI-cpp2.cpp to the swipl_plugin stanza.