chacham15
chacham15

Reputation: 14251

Is there an inline way to mix c and c++ prototypes?

I want an inline way of specifying which prototypes should be included with c++. For example:

       void ArrayList_insert(ArrayList *arrlst, void *data, int i);
IS_CPP void ArrayList_insert(ArrayList *arrlst, char *data, int i);
IS_CPP void ArrayList_insert(ArrayList *arrlst, Buffer *data, int i);

currently I am doing:

#ifdef __cplusplus
extern "C" {
#endif

....C HEADERS..

#ifdef __cplusplus
}

....C++ HEADERS...

#endif

but its very inconvenient because overloads of the same function are in different places. I could just have 2 different header files, but that is a pain too. Therefore, Im looking for an inline solution like i proposed above. Does anyone know of a way to do that?

Upvotes: 10

Views: 623

Answers (5)

Carl Norum
Carl Norum

Reputation: 224944

Sure, you can do it almost like your example by using a function-like macro:

#ifdef __cplusplus
#define IS_CPP(x) x
#else
#define IS_CPP(x)
#endif

       void ArrayList_insert(ArrayList *arrlst, void *data, int i);
IS_CPP(void ArrayList_insert(ArrayList *arrlst, char *data, int i));
IS_CPP(void ArrayList_insert(ArrayList *arrlst, Buffer *data, int i));

Now if you compile the header as C++, you'll get all three, but if you compile as C, you'll only get the one. If you want to share a single library between the two, you'll need to add some extern "C" qualifiers to the C function when compiling for C++. @MarkLakata's answer shows one possible way.

Upvotes: 11

Pete Becker
Pete Becker

Reputation: 76295

The usual approach is to just write it in the most obvious way:

void ArrayList_insert(ArrayList *arrlst, void *data, int i);
#ifdef __cplusplus
void ArrayList_insert(ArrayList *arrlst, char *data, int i);
void ArrayList_insert(ArrayList *arrlst, Buffer *data, int i);
#endif /* __cplusplus */

As @chacham15 points out, you also need, in a project-wide header,

#ifdef __cplusplus
#define EXTERN_C extern "C"
#endif /* __cplusplus */

and you need to decorate the C-callable function with EXTERN_C.

Upvotes: 3

Potatoswatter
Potatoswatter

Reputation: 137810

If you really want to get rid of the boilerplate and you are willing to use the preprocessor to do it, then just go ahead and write up the pattern. The general pattern you have looks like

extern "C" {
    void C_accessible_declaration(); // this is all C sees
}

void Cxx_accessible_declaration_1( int );
void Cxx_accessible_declaration_1( long );

So you could make a macro,

#ifdef __cplusplus
#   define C_PORTABLE_FUNCTION_SET( C_DECLS, CXX_DECLS ) \
        extern "C" { C_DECLS } \
        CXX_DECLS
#else
#   define C_PORTABLE_FUNCTION_SET( C_DECLS, CXX_DECLS ) \
        C_DECLS
#endif

This works because an ordinary function declaration cannot contain a comma not enclosed by parentheses. If you want it to work with templates (with comma-separated template parameters), then you can variadic macros, supported in C99, C++11, and various compilers preceding those standards as an extension.

#ifdef __cplusplus
#   define C_PORTABLE_FUNCTION_SET( C_DECLS, ... ) \
        extern "C" { C_DECLS } \
        __VA_ARGS__
#else
#   define C_PORTABLE_FUNCTION_SET( C_DECLS, ... ) \
        C_DECLS
#endif

Now this works as long as the C declarations do not contain a naked comma, which means you shouldn't declare multiple objects in the one declaration. I've called it C_PORTABLE_FUNCTION_SET to emphasize it's mainly safe for use with function declarations, but note that you need to declare C-accessible objects within extern C as well. Shared struct definitions should not be protected at all; they are protected by the C++ POD concept, and do not carry language linkage.

Usage:

#ifdef __cplusplus
template< typename T, typename U >
class Buffer { // still use #ifdef for the general case
    ...
};
#endif

C_PORTABLE_FUNCTION_SET (
        void ArrayList_insert(ArrayList *arrlst, void *data, int i);
, /* C++ */
        void ArrayList_insert(ArrayList *arrlst, char *data, int i);

        template< typename T, typename U >
        void ArrayList_insert(ArrayList *arrlst, Buffer< T, U > &data, int i);
)

I don't think I'd do this myself, but it seems safe enough to become idiomatic.

Upvotes: 1

user539810
user539810

Reputation:

You can obviously abuse the preprocessor to hack together what you're asking, but why do it? Personally I would rather type mylst.insert(foop, 1); instead of ArrayList_insert(mylst, foop, 1); if I was using C++. In other words, I see little benefit in using the C style for calling the overloaded functions, but mixing the styles of the function calls that you as the code author created isn't exactly pretty either.

I'd make an ArrayList class that has the same members as in the C structure, and anytime you need to interface your class with the C functions, create a shallow copy if possible and pass that to the C function, then copy the information from that structure back into your class.

An alternative would be to wrap the structure in a C++ class and use that for the C interface functions.

Otherwise, you might try making the class inherit the C structure, assuming the structure's tag isn't named ArrayList and the typedef for the structure isn't visible from the C++ interface. Then you can hopefully pass the this pointer directly from within the member function as if it was the actual C structure. I'm not sure this method is portable in all cases, so I'd implement the former idea if possible, even if it does require copying data back and forth.

All ideas avoid code duplication, and the C++ interface looks more like C++ code instead of the bad mixture of C functions and C++ function overloading. Additionally, the interfaces are kept separate somewhat. No additional header files are necessary either as the C functions can be wrapped in an extern "C" block as usual:

#ifdef __cplusplus
extern "C" {
#endif

struct array_list_tag {
  ...
};

/* C functions here */

#ifdef __cplusplus
} /* extern "C" */

class ArrayList ...
#else /* !__cplusplus */
typedef struct array_list_tag ArrayList;
#endif

Upvotes: 1

Mark Lakata
Mark Lakata

Reputation: 20838

It's easier than you think.

#ifdef __cplusplus
#define IS_C(x)   extern "C" x ;
#define IS_CPP(x) x ;
#else
#define IS_C(x)   x ;
#define IS_CPP(x) 
#endif

With this type of header:

IS_C   (void ArrayList_insert(ArrayList *arrlst, void *data, int i))
IS_CPP (void ArrayList_insert(ArrayList *arrlst, char *data, int i))
IS_CPP (void ArrayList_insert(ArrayList *arrlst, Buffer *data, int i))

Upvotes: 14

Related Questions