On 10/10/2017 03:48 PM, Joseph Myers wrote:
On Tue, 10 Oct 2017, Martin Sebor wrote:

Calling a function that takes arguments via a void (*)(void)
is undefined not just on paper but also in practice, so the
resulting pointer from such a cast is unusable except to convert
to a compatible pointer.

That's the point of a generic pointer - it either gets cast to the actual
type (or something ABI-compatible with the actual type) for calls, or
called from non-C code which has the same effect.

Sure.  But (as you note below) any function pointer can be used
as a generic function pointer.  There's nothing unique or special
about void (*)(void) to make it particularly suitable for this
purpose.

But we're conflating two use cases: 1) converting to a generic
function pointer that's never used to call a function, and 2)
converting to a function pointer that's used to call a function
not strictly compatible with it but where the ABI obviates
the incompatibility.

The ideal solution for 1) would be a function pointer that can
never be used to call a function (i.e., the void* equivalent
for functions).[X]

I argue the ideal solution for 2) is void (*)() (or something
like it).  It's superior to void(*)(void) because it makes its
intent in a cast clear: ignore the types of the target function's
formal arguments.  Contrast that with void(*)(void) which could
mean one of two things: a) follow the type system rules, or b)
treat it as special and ignore the type system.  Accepting (b)
when (a) so clearly expresses the intent of this use case seems
like a clearly inferior choice to me.

PS It would be useful if modern C provided a clean mechanism to
support generic function pointers rather that forcing users leery
of relying on obsolescent features to invent workarounds.  One

You can use any function pointer type whatever as your generic pointer
(casting back to the original type for calls).  That you can cast from one
function pointer type to another and back to the original type is
guaranteed by ISO C.  That the result of the conversion is meaningful for
calls from non-C languages is part of the customary practices of C
implementations.  That the type used is void (*) (void) is a common
convention used in C code dealing with generic function pointers (another
convention, of course, is just using void * as in POSIX and relying on
conversions between function pointers and void *).

Right.  The problem Bernd and I are trying to solve is how to
distinguish the last one from the other two use cases on this
list:

1) a conversion of an arbitrary function to a generic pointer
   that's only used for storage but never to call the function
   directly, without converting it to the original function's
   type (see also [X] below),
2) a conversion of an arbitrary function to some strictly
   incompatible type that is nonetheless safe to use (by the
   ABI rules) to call the original function, and
3) all other conversions that are likely mistakes/bugs and
   that should be diagnosed.

My claim, again, is that using void(*)() for both (1) and (2)
above (or a moral equivalent of it, such as void(*)(...)) is
a superior solution than any other that we have seen, including
void(*)(void), because it clearly expresses the intent (no type
checking) and doesn't exclude any type from the set to check for
compatibility.

Incidentally, void(*)(void) in C++ is a poor choice for this
use case also because of the language's default function
arguments.  It's an easy mistake for a C++ programmer to make
to assume that given, say:

  void foo (const char *s = "...");

or for any other function that provides default values for all
its arguments, the function may be callable via void(*)(void):

  typedef void F (void);

  void (pf)(void) = (F*)foo;

by having the (default) function argument value(s) magically
substituted at the call site of:

   pf ();

Bu since that's not the case it would be helpful for the new
warning to detect this mistake.  By encouraging the use of

  typedef void F (...);

as the type of a pointer there is little chance of making such
a mistake.

Martin

[X] This can be function that takes an argument of an incomplete
type, such as:

  struct Incomplete;
  typedef void Uncallable (struct Incomplete);

Any function can safely be converted to Uncallable* without
the risk of being called with the wrong arguments.  The only
way to use an Uncallable* is to explicitly convert it to
a pointer to a function that can be called.

Reply via email to