Let's say I have a type in a header, used by C/C++-code, looking like this:
typedef struct Point
{
double X, Y, Z;
} Point ;
It is usually allocated on the stack, like this.
Point point;
memset(&point, 0, sizeof(point));
This initialization is mostly performed in C++-code, and only rarely in C-code. The C-code still needs to know about this type, however, as the C-code performs operations on it.
e.g.
C_API void mutatePoint(Point *point);
My question is therefore, would it be safe to add a conditional C++-constructor, to avoid having to call memset every time?
typedef struct Point
{
double X, Y, Z;
#ifdef __cplusplus
Point() { X = Y = Z = 0.0; }
Point(double x, double y, double z) { X = x; Y = y; Z = z; }
#endif
} Point;
This would simplify initialization and usage in C++-code:
Point point;
mutatePoint(&point); // Already set to (0, 0, 0) due to constructor, no need to memset first
When I talk about safe, I mean does it violate any C/C++-rules such as ODR? Or would this be OK to do. I'm OK with it not being recommended, and I'm also OK with hearing any inputs on why it would it would not be recommended.
But I'm mainly wondering whether or not there's a rule that explicitly allows/disallows this. I can confirm that such code compiled with msvc works seemingly ok, and has done so for many years.
it is safe, it is not illegal. since all C++ translation units see the same code, there is no ODR violations. but it is very confusing when the same code has different behavior when compiled with a C++ and a C compiler.
in C++ you can zero initialize a struct as follows.
Point point{}; // zero (value) initialized
Point point; // uninitialized
other ways to have a good C++ API method is that you can make the C++ type inherit the C type, as mentioned by @Ted Lyngmo in the comments and as done by microsoft in CPoint.
typedef struct Point
{
double X, Y, Z;
} Point ;
void mutate_point(Point*); // C API
struct CPoint : public Point
{
CPoint() : Point{} {}
CPoint(const Point& p) : Point{ p } {}
CPoint(double x, double y, double z) : Point{ x,y,z } {}
void mutate() { mutate_point(this); }
};
int main()
{
Point b = CPoint(); // compiles!
CPoint c;
mutate_point(&c); // also compiles
}
Another is declaring a C++ wrapper type with an implicit conversion operator to a reference to the C type, similar to vulkan-hpp (but not equivalent, what they actually do is UB, this is not)
struct CPoint
{
Point p{};
operator Point& () { return p; }
operator const Point& () const { return p; }
void mutate() { mutate_point(&p); }
};
Point b = CPoint(); // compiles!
you can get mutate_point(CPoint&)
to compile using ADL if you really want it.