6

In C, it's not an error to cast pointers to and from void *.

A major obstacle in porting to C++ is the need to cast pointers when returning from functions dealing with generic pointers such as malloc, and functions declared in my own code such as void *block_get(Blkno const blkno);.

My code however is intended to be compiled by C and C++ compilers successfully. If I provide explicit casts everywhere for the sake of C++, they must be C-style casts and I may be masking bugs due to casting non-pointer types to and from pointer types from both languages.

My reference error is the following:

struct Cpfs *cpfs = calloc(1, sizeof(*cpfs));

which in MSVC produces:

Error 2 error C2440: 'initializing' : cannot convert from 'void *' to 'Cpfs *' e:\src\cpfs\cpfs.c 179

Evidently I can't use new or static_cast which I'd naturally use if I was no longer using C. What's the best way to provide maximum type safety surrounding void *for each language with minimal verbosity?

Matt Joiner
  • 112,946
  • 110
  • 377
  • 526
  • 4
    Why do you need to port to the common subset of C and C++? As you've discovered it severely limits what C (alternatively what C++) you can use. Almost all environments that have C++ available also have C available and also allow you to link C++ and C object files together. I can't see what gain you get by porting to the common subset. – CB Bailey Oct 03 '10 at 09:38
  • For *type safety in C* (which is a bit hard), make a function for each struct whose job is to allocate and return a pointer to a new struct. Likewise, make a function for freeing the struct. – rwong Oct 03 '10 at 09:38
  • 1
    I'd like to know the rationale for trying to write a program that compiles for two different languages. That's typically something left for puzzles. – GManNickG Oct 03 '10 at 09:54
  • BTW, in C++, you should be using `static_cast` to cast to/from `void*`. – jamesdlin Oct 03 '10 at 09:56
  • @GMan: the times I've done it have always been `static inline` (or implementation-defined equivalent, in the case of C89) functions in headers relating to a library mostly written in C, but used by both. When things get too hairy trying to work in the intersection, you can give up on that function being `static inline`, and just write it in C. – Steve Jessop Oct 03 '10 at 12:36
  • 1
    9 times out of 10, `static inline` in header files is bogus premature optimization. – R.. GitHub STOP HELPING ICE Oct 04 '10 at 15:24
  • @R. Fortunately, I've only done it 10% of the time, and I know my code base better than you do. – Steve Jessop Oct 04 '10 at 22:16
  • possible duplicate of [When convert a void pointer to a specific type pointer, which casting symbol is better, static\_cast or reinterpret\_cast?](http://stackoverflow.com/questions/5013596/when-convert-a-void-pointer-to-a-specific-type-pointer-which-casting-symbol-is) – BЈовић Oct 11 '13 at 15:47

5 Answers5

5

I'd suggest either simply using C style casts, or wrapping the cast in a macro that either expands to nothing (in C), or a static_cast in C++.

Hasturkun
  • 35,395
  • 6
  • 71
  • 104
  • 3
    A `reinterpret_cast` from `void*` is not the best cast. `static_cast` is sufficient and less likely to silently cast an invalid source to a pointer type. – CB Bailey Oct 03 '10 at 10:00
2

If your compiler supports decltype(), you can use some macro magic to avoid having to explicitly repeat the type name (and, thanks to sizeof, the element size):

#ifdef __cplusplus
#define my_calloc(VAR, COUNT) \
    static_cast<decltype(VAR)>(std::calloc(COUNT, sizeof *VAR))
#else
#define my_calloc(VAR, COUNT) calloc(COUNT, sizeof *VAR)
#endif

Example usage:

#ifdef __cplusplus
#include <cstdlib>
#else
#include <stdlib.h>
#endif

struct Cpfs *cpfs = my_calloc(cpfs, 42);

The cleaner solution would probably be to just use a C compiler and link the object files, though...

Christoph
  • 164,997
  • 36
  • 182
  • 240
  • decltype looks similar to typeof, using a c compiler isn't an option, msvc has no c99 support. c++ compiles it better than a half-assed c compiler – Matt Joiner Oct 03 '10 at 11:43
  • 1
    @Matt: you could use MinGW to compile the C part of your project and still use MSVC for the C++ part (see eg http://stackoverflow.com/questions/2096519/from-mingw-static-library-a-to-visual-studio-static-library-lib ) – Christoph Oct 03 '10 at 11:56
  • @Matt Joiner: programming to the intersection of C99 and C++ isn't substantially different from just programming C89, it's just more awkward and annoying. You're writing to a half-assed version of C as it is, using a half-assed C compiler isn't any worse, and at least C89's a standard. – Steve Jessop Oct 03 '10 at 13:23
1

make a replacement allocator function that you can define differently for C and C++ builds :- Something like this in a header file:

#ifdef __cplusplus
template<typename TypeT>
TypeT* MyAlloc(TypeT** pOut,size_t cb){
  *pOut = static_cast<TypeT*>(malloc(cb)); //aint c++ pretty.
  return *pOut;
}
#else
  extern void* MyAlloc(void** ppv, size_t cb);
#endif

Now you have, in c++ builds, a function that can infer the type of thing its dealing with, and in C builds, its a regular function that returns a void*.

The only problem is the need to pass in the pointer to allocate - the c++ compiler wont try to deduce a template parameter based only on the return type of a function afaik. So you could call it agnostically like this :-

int *p;
if(MyAlloc(&p,sizeof(int)*n)){
  ...
Chris Becke
  • 34,244
  • 12
  • 79
  • 148
  • "//aint c++ pretty." Yeah, when you use `new`. – GManNickG Oct 03 '10 at 10:07
  • 1
    `new` is even worse as c++ compilers dont deduce class template parameters from the constructor call. So I had to write something like this the other day: `SomeClass* pWrapper = new SomeOtherClass(this,&SomeOtherClass::method);` – Chris Becke Oct 03 '10 at 10:49
  • 1
    @Chris: You're using `new` without putting it in a wrapper, your point is moot to me. C++0x has `auto`, and your response is a red-herring anyway. – GManNickG Oct 03 '10 at 21:17
  • Its now a best practice to always bundle calls to new in a wrapper? `auto` only solves half the problem. and new is no more pretty than malloc() especially if the recommended way to avoid the ugliness is to wrap it. – Chris Becke Oct 04 '10 at 12:40
  • 2
    @Chris: Uh, yeah. That's not even a new idea, it's relatively old. (As in, we've been in a "modern C++" era, where you don't manage your resources manually. Wrap them up (`std::vector`, `shared_ptr`, etc.); it's scope-bound resource management (SBRM) more known as it's ugly name RAII) Any time you have to manually free something, you've done something wrong. Again, this tangent is a red-herring. The code you said was ugly in your post is only ugly because it's not idiomatic C++, replying with some *other* code is irrelevant. – GManNickG Oct 04 '10 at 23:12
0

The only solution I know is to do explicit casting:

struct Cpfs *cpfs = (Cpfs*)calloc(1, sizeof(*cpfs));

Here both compilers are satisfied. Also that remember, that for older compilers malloc may return char*.

hth

Mario

Mario The Spoon
  • 4,799
  • 1
  • 24
  • 36
0

Maybe something like this? (untested, no compiler available, not using macros very often):

#ifdef __cplusplus
    #define pointer_cast(type, pointer) reinterpret_cast<type>(pointer)
#else
    #define pointer_cast(type, pointer) (type)(pointer)
#endif
Milan
  • 3,342
  • 3
  • 31
  • 40