I am aware that in C you can't implicitly convert, for instance, char**
to const char**
(c.f. C-Faq, SO question 1, SO Question 2).
On the other hand, if I see a function declared like so:
void foo(char** ppData);
I must assume the function may change the data passed in. Therefore, if I am writing a function that will not change the data, it is better, in my opinion, to declare:
void foo(const char** ppData);
or even:
void foo(const char * const * ppData);
But that puts the users of the function in an awkward position. They might have:
int main(int argc, char** argv) { foo(argv); // Oh no, compiler error (or warning) ... }
And in order to cleanly call my function, they would need to insert a cast.
I come from a mostly C++ background, where this is less of an issue due to C++'s more in-depth const rules.
What is the idiomatic solution in C?
Declare foo as taking a char**
, and just document the fact that it won't change its inputs? That seems a bit gross, esp. since it punishes users who might have a const char**
that they want to pass it (now they have to cast away const-ness)
Force users to cast their input, adding const-ness.
Something else?
Although you already have accepted an answer, I'd like to go for 3) namely macros. You can write these in a way that the user of your function will just write a call foo(x);
where x can be const
-qualified or not. The idea would to have one macro CASTIT
that does the cast and checks if the argument is of a valid type, and another that is the user interface:
void totoFunc(char const*const* x); #define CASTIT(T, X) ( \ (void)sizeof((T const*){ (X)[0] }), \ (T const*const*)(X) \ ) #define toto(X) totoFunc(CASTIT(char, X)) int main(void) { char * * a0 = 0; char const* * b0 = 0; char *const* c0 = 0; char const*const* d0 = 0; int * * a1 = 0; int const* * b1 = 0; int *const* c1 = 0; int const*const* d1 = 0; toto(a0); toto(b0); toto(c0); toto(d0); toto(a1); // warning: initialization from incompatible pointer type toto(b1); // warning: initialization from incompatible pointer type toto(c1); // warning: initialization from incompatible pointer type toto(d1); // warning: initialization from incompatible pointer type }
The CASTIT
macro looks a bit complicated, but all it does is to first check if X[0]
is assignment compatible with char const*
. It uses a compound literal for that. This then is hidden inside a sizeof
to ensure that actually the compound literal is never created and also that X
is not evaluated by that test.
Then follows a plain cast, but which by itself would be too dangerous.
As you can see by the examples in the main
this exactly detects the erroneous cases.
A lot of that stuff is possible with macros. I recently cooked up a complicated example with const
-qualified arrays.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With