Hi Bruno, On Thu, Jan 15, 2026 at 11:04:37AM +0100, Bruno Haible wrote: > Alejandro Colomar wrote: > > I've had the idea today of an implementation that would be even better > > for users, not requiring the cast at all. > > > > $ cat execve4.c > > #include <stddef.h> > > > > #ifdef __cplusplus > > # define const_cast(T, p) p > > #else > > # define const_cast(T, p) _Generic(p, const T: (T) (p), default: (p)) > > #endif > > > > #ifdef __cplusplus // Have n3749 > > int execve(const char *path, const char *const a[], const char *const > > e[]); > > #else > > # define execve(p, a, e) execve(p, const_cast(char*const*,a), > > const_cast(char*const*,e)) > > int (execve)(const char *path, char *const a[], char *const e[]); > > #endif > > Very nice!
Thanks! :-)
> This can even be done in Gnulib, without waiting for libc changes.
I have something even better for Gnulib. See below.
> And for older compilers (that don't support _Generic), we can use an explicit
> cast:
>
> #ifdef __cplusplus
> # define const_cast(T, p) p
> #elif (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 9) > 4) \
> || (defined __clang__ && __clang_major__ >= 3) \
> || (defined __SUNPRO_C && __SUNPRO_C >= 0x5150) \
> || (__STDC_VERSION__ >= 201112L && !defined __GNUC__)
> # define const_cast(T, p) _Generic(p, const T: (T) (p), default: (p))
> #else
> # define const_cast(T, p) (T) (p)
> #endif
>
> > The only place programmers would notice the different prototype would be
> > in function pointers, but that's where Chris's proposal N3674 would kick
> > in. It would allow unidirectional implicit conversions of functions
> > where one is more restrictive than the other regarding qualifiers.
>
> I see. Yes, then N3674 makes a lot of sense: The language would feel
> incomplete if this implicit conversion from 'char **' to 'const char **'
> was allowed in argument passing but not in function pointer types.
Yup.
BTW, I wrote an implementation where you still get the old prototype
when using function pointers, for compilers without N3674. This one,
you could already merge in Gnulib, I think.
#include <stddef.h>
#include <unistd.h>
#ifdef __cplusplus
# define CONST_CAST(T, p) const_cast<T>(p)
#else
# define CONST_CAST(T, p) _Generic(p, const T: (T) (p), default: (p))
#endif
#define execve_casted(p, a, e) execve(p, CONST_CAST(char*const*,a),
CONST_CAST(char*const*,e))
static inline int
execve_const(const char *path, const char *const a[], const char *const
e[])
{
return execve_casted(path, a, e);
}
#ifdef __cplusplus // Have n3749
# define execve(...) execve_const(__VA_ARGS__)
#else
# define execve(...) execve_casted(__VA_ARGS__)
#endif
typedef int Told(const char *, char *const[], char
*const[]);
typedef int Tnew(const char *, const char *const[], const char
*const[]);
int
main(void)
{
char * argv [] = {"foo", "bar", NULL}; //
-Wdiscarded-qualifiers
const char * cargv [] = {"foo", "bar", NULL};
char *const argvc[] = {"foo", "bar", NULL}; //
-Wdiscarded-qualifiers
const char *const cargvc[] = {"foo", "bar", NULL};
Told *fpold;
Tnew *fpnew;
execve("foo", argv , NULL);
execve("foo", cargv , NULL); // error:
-Wincompatible-pointer-types; fixed by n3749
execve("foo", argvc, NULL);
execve("foo", cargvc, NULL);
fpold = execve;
fpold = execve_const; // error: -Wincompatible-pointer-types;
should be fixed by n3674
//fpnew = execve; // error: -Wincompatible-pointer-types;
fpnew = execve_const;
}
If you take this, please add:
Co-authored-by: Alejandro Colomar <[email protected]>
Have a lovely night!
Alex
--
<https://www.alejandro-colomar.es>
signature.asc
Description: PGP signature
