[ Adding namespaces to C++ implementations that have a C header ]
We have a large project with C and C++ code.
For every C++ implementation, apart from the C++ header, we usually have provide a C-header to allow functionality to be available for .c files, also.
So, most of our files look like so:
foo.hpp:
class C {
int foo();
};
foo.h:
#ifdef __cplusplus
extern "C" {
typedef struct C C; // forward declarations
#else
class C;
#endif
int foo( C* ); // simply exposes a member function
C* utility_function( C* ); // some functionality *not* in foo.hpp
#ifdef __cplusplus
}
#endif
foo.cpp:
int C::foo() { /* implementation here...*/ }
extern "C"
int foo( C *p ) { return p->foo(); }
extern "C"
C* utility_function ( C* ) { /* implementation here...*/ }
QUESTION:
Suppose I wanted to add a namespace to the class like so:
foo.hpp:
namespace NS {
class C {
int foo();
};
}
what is the best scheme to follow in the C-headers?
I have considered a few options, but I'm looking for the most elegant, safe and easy to read. Is there a standard way you use?
Here are the options I've considered:
(I've ommitted the extern "C"
constructs for simplicity)
- Option 1: fool the compiler by adding some code in each header:
foo.h
#ifdef __cplusplus
namespace NS { class C; } // forward declaration for C++
typedef NS::C NS_C;
#else
struct NS_C; // forward declaration for C
#endif
int foo( NS_C* );
NS_C* utility_function( NS_C* );
this adds some complexity to the header, but keeps the implementations unchanged.
Option 2: Wrap the namespace with a C-struct:
Keeps the header simple but makes the implementation more complex:
foo.h
struct NS_C; // forward declaration of wrapper (both for C++ and C)
int foo( NS_C* );
NS_C* utility_function( NS_C* );
foo.cpp
namespace NS {
int C::foo() { /* same code here */ }
}
struct NS_C { /* the wrapper */
NS::C *ptr;
};
extern "C"
int foo( NS_C *p ) { return p->ptr->foo(); }
extern "C"
NS_C *utility_function( NS_C *src )
{
NS_C *out = malloc( sizeof( NS_C ) ); // one extra malloc for the wrapper here...
out->ptr = new NS::C( src->ptr );
...
}
are these the only schemes? Are there any hidden disadvantages in any of these?
Answer 1
I find it easier to factor code in a way so that foo.h
only contains the bare minimum of C++ specifics while foo.hpp
takes care of the gritty bits.
The file foo.h contains the C API and should not be included directly from C++ code:
#ifndef NS_FOO_H_
#define NS_FOO_H_
// an incomplete structure type substitutes for NS::C in C contexts
#ifndef __cplusplus
typedef struct NS_C NS_C;
#endif
NS_C *NS_C_new(void);
void NS_C_hello(NS_C *c);
#endif
The file foo.hpp contains the actual C++ API and takes care of including foo.h into C++ files:
#ifndef NS_FOO_HPP_
#define NS_FOO_HPP_
namespace NS {
class C {
public:
C();
void hello();
};
}
// use the real declaration instead of the substitute
typedef NS::C NS_C;
extern "C" {
#include "foo.h"
}
#endif
The implementation file foo.cpp is written in C++ and thus includes foo.hpp, which also pulls in foo.h:
#include "foo.hpp"
#include <cstdio>
using namespace NS;
C::C() {}
void C::hello() {
std::puts("hello world");
}
C *NS_C_new() {
return new C();
}
void NS_C_hello(C *c) {
c->hello();
}
If you do not want to make the C API available to C++ code, you could move the relevant parts from foo.hpp to foo.cpp.
As an example for use of the C API a basic file main.c:
#include "foo.h"
int main(void)
{
NS_C *c = NS_C_new();
NS_C_hello(c);
return 0;
}
This example has been tested with the MinGW edition of gcc 4.6.1 using the following compiler flags:
g++ -std=c++98 -pedantic -Wall -Wextra -c foo.cpp
gcc -std=c99 -pedantic -Wall -Wextra -c main.c
g++ -o hello foo.o main.o
The code assumes that the types NS::C *
and struct NS_C *
have compatible representation and alignment requirements, which should be the case virtually everywhere, but as far as I know is not guaranteed by the C++ standard (feel free to correct me if I'm wrong here).
From a C language perspective, the code actually invokes undefined behaviour as you're technically calling a function through an expression of incompatible type, but that's the price for interoperability without wrapper structures and pointer casts:
As C doesn't know how to deal with C++ class pointers, the portable solution would be to use void *
, which you should probably wrap in a structure to get back some level of type safety:
typedef struct { void *ref; } NS_C_Handle;
This would add unnecessary boilerplate on platforms with uniform pointer representation:
NS_C_Handle NS_C_new() {
NS_C_Handle handle = { new C() };
return handle;
}
void NS_C_hello(NS_C_Handle handle) {
C *c = static_cast<C *>(handle.ref);
c->hello();
}
On the other hand, it would get rid of the #ifndef __cplusplus
in foo.h, so it's actually not that bad, and if you care about protability, I'd say go for it.
Answer 2
I don't fully understand what you're trying to do, but this may help:
If you want it still accessible by C then do this:
void foo();
namespace ns {
using ::foo;
}
Or use a macro:
#ifdef __cplusplus
#define NS_START(n) namespace n {
#define NS_END }
#else
#define NS_START(n)
#define NS_END
#endif
NS_START(ns)
void foo();
NS_END
Answer 3
Your header is all messed up.
You probably want something more like:
struct C;
#ifdef __cplusplus
extern "C" {
#else
typedef struct C C;
#endif
/* ... */
#ifdef __cplusplus
}
#endif