Rather than macro hackery to work around the fact that C does not support default arguments, I'd introduce a layer of indirection.
First a C++ specific header that your C++ code uses (which I arbitrarily name interface.h
.
double my_function_caller(double x, double abs_error = 1E-3);
and a C specific header (which I arbitrarily name the_c_header.h
)
double my_function(double x, double abs_error);
/* all other functions that have a C interface here */
In practice, one would probably want include guards in both headers.
The next step is a C++ compilation unit (which I arbitrarily name interface.cpp
) that actually interfaces to mathematica
#include "interface.h"
extern "C" // this is C++, so we don't need to test __cplusplus
{
#include "the_c_header.h"
}
double my_function_caller(double x, double error)
{
return my_function(x, error);
}
Then there is just the question of how to call the function. If the caller is C++, then all it needs to do is
#include "interface.h"
// and later in some code
double result = my_function_caller(x);
double another_result = my_function_caller(x, 1E-6);
If the caller is C (built with a C compiler) it simply does
#include "the_c_header.h"
/* and later */
result = my_function(x, 1E-3);
another result = my_function(x, 1E-6);
There are obviously advantages and disadvantages of this, compared with a macro-based solution, including;
- None of the traditional disadvantages of macros (not respecting scope, no unintended interactions with other macros, running afoul of some C++ development guidelines that forbid usage of macros for anything except include guards).
- Clear separation of which code is C and which is C++: Only
interface.cpp
needs to take care to have both #include "the_c_header.h"
and #include "interface.h"
and worry about the mechanics of interfacing of C++ to C. Otherwise, C compilation units (compiled with a C compiler) only need #include "the_c_header.h"
and C++ compilation units only need to #include "interface.h"
.
interface.h
can use any C++ language features (not just default arguments). For example, all the functions it declares may be placed in a namespace named mathematica
if you wish. C++ developers using your functions need not care that there is actually an interface to C code buried away within that call.
- If you decide in future to re-implement
my_function()
using something other than mathematica you can. Simply drop in the replacements of the_c_header.h
and interface.cpp
, and rebuild. The separation of concerns means that it is unnecessary to change interface.h
, that all C++ callers will not even need to be recompiled in an incremental build (unless, of course, you change interface.h
for some other reason).
- Practically, the build process will detect mistaken usage of both header files. A C compiler will choke on
interface.h
because it uses C++-specific features. A C++ compiler will accept contents of the_c_header.h
outside of an extern "C"
context, but the result will be a linker error if any C++ code ever calls my_function()
directly (linking will require a name-mangled definition).
In short, this takes a little more effort to set up than the macro approach, but is easier to maintain in the long run.