1

I have a C++ library, with functions declared in a header file. My function declarations include default arguments.

I would like to use this library in Mathematica via the Wolfram Mathematica WSTP Template Compiler (wscc). This requires writing a C interface to my library. I have used this pattern

#ifdef __cplusplus
extern "C" {
#endif

double my_function(double x, double abs_error = 1E-3);

#ifdef __cplusplus
}
#endif

to prevent name-mangling in my library (compiled with C++). But what about the default arguments? I don't think they're standard C. From Wolfram Mathematica WSTP Template Compiler (wscc), I find

error: expected ‘;’, ‘,’ or ‘)’ before ‘=’ token double abs_error = 1E-3,

Do I have to make separate C and C++ declarations (essentially two header files)? Is this a common problem or is it related to my use of wscc? Perhaps wscc doesn't support this syntax, although it is usually acceptable?

innisfree
  • 2,044
  • 1
  • 14
  • 24

3 Answers3

11

C does not support default arguments.

I'm therefore assuming you want to keep them for your C++ code, but you're okay with requiring C callers (in your case, Mathematica) to pass values for all arguments.

One possible approach is to define a macro which expands to the default value initializer in C++, but to nothing in C. It's not pretty, but it works:

#ifdef __cplusplus
#define DEFAULT_VALUE(x) = x
#else
#define DEFAULT_VALUE(x)
#endif

#ifdef __cplusplus
extern "C" {
#endif

void foo(int x DEFAULT_VALUE(42), void *y DEFAULT_VALUE(nullptr));

// In C, this becomes   void foo(int x, void *y);
// In C++, this becomes void foo(int x = 42, void *y = nullptr);

#ifdef __cplusplus
}
#endif
jtbandes
  • 115,675
  • 35
  • 233
  • 266
1

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.

Peter
  • 35,646
  • 4
  • 32
  • 74
0

The extern C does more than stop name mangling. The function has C calling conventions. It's telling the CPP compiler "you should now speak C". That means exceptions won't propagate, etc. And AFAIK C doesn't have default values.

You can have a function with C calling conventions implemented inside a CPP file, which is very useful sometimes. You can have C calling bits of your CPU code, which is useful.

My suspicion is that how a compiler goes about dealing with default values is up to the compiler writer. If that's true, I can think of at least a couple of ways, one of them not involving putting a value for abs_error on the stack when my_function is called. For example the compiler might include a parameter count on the stack which the function itself uses to spot that abs_error has not been passed and to set a default value. However such a compiler would be very unfriendly indeed if it did that with a function that had C calling conventions without reporting errors. I think I'd test it, just to be sure.

bazza
  • 7,580
  • 15
  • 22
  • _"how a compiler goes about dealing with default values is up to the compiler writer"_ — I don't believe that's true... – jtbandes Jul 10 '17 at 02:36
  • 1
    @jtbandes, I looked for something definitive on that topic, and came up with nothing, hence my cautious words. My usual reference of choice is silent on the matter. I would welcome a definitive link! If it is defined in the draft c++14 standard it is not making itself massively obvious in section 8.3.6. I haven't fully worked out what the consequences of 8.3.6.5 mean to compiler implementers. – bazza Jul 10 '17 at 05:48