Path: csiph.com!news.swapon.de!eternal-september.org!feeder3.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail
From: Tim Rentsch
Newsgroups: comp.lang.c++
Subject: Re: Proper cast of function pointers
Date: Wed, 24 Apr 2024 15:10:52 -0700
Organization: A noiseless patient Spider
Lines: 177
Message-ID: <86frvawgwj.fsf@linuxsc.com>
References:
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Injection-Date: Thu, 25 Apr 2024 00:10:54 +0200 (CEST)
Injection-Info: dont-email.me; posting-host="4d027413e75dd4b7b3c31e0f92925554"; logging-data="2705299"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1+rPiPBtMBcnRcBjrM4pgq0g4kJ4eh1f3A="
User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.4 (gnu/linux)
Cancel-Lock: sha1:Yi04ptzk8d7WQW9KpS1/I0ILPzg= sha1:FctKbTVoV9Vo72j0W1QEYE3yJTg=
Xref: csiph.com comp.lang.c++:118879
Paavo Helde writes:
> 24.04.2024 10:36 Bonita Montero kirjutas:
>
>> Am 23.04.2024 um 20:33 schrieb Paavo Helde:
>>
>>> The function pointers are cast to a single type so that they can be
>>> stored in a common lookup array. ...
>>
>> Then you'd need additional information to distinguish the different
>> types. If you have sth. like that you could take a variant<>.
>
> Right, the varying part is the number of arguments, which is
> explicitly declared and stored in the array as well (n_pars below). If
> you are interested, the current code (no warnings any more here,
> thanks to Markus!) looks like below. Not sure if changing to a variant
> or void(*)() would make the code better, looks like then I would need
> to add extra casts to all the lines in the table which currently do
> not need any casts.
>
> #include
>
> typedef double (*Func)(double);
>
> struct formu_item {
> const char *name;
> Func f; /* pointer to function*/
> int n_pars; /* number of parameters (0, 1, 2 or 3) */
> int varying; /* Does the result of the function vary
> even when the parameters stay the same?
> varying=1 for e.g. random-number generators. */
> };
>
> typedef void (*VoidFunc)();
> typedef double (*DoubleFunc_0_args)();
> typedef double (*DoubleFunc_2_args)(double, double);
>
> Func FuncCast2(DoubleFunc_2_args fp) {
> return reinterpret_cast(reinterpret_cast(fp));
> }
> Func FuncCast0(DoubleFunc_0_args fp) {
> return reinterpret_cast(reinterpret_cast(fp));
> }
> double pi() {
> return 3.1415926535897932384626433832795029;
> }
>
> static const formu_item ftable_static[TABLESIZE]=
> {
> {"exp", exp,1,0},
> {"ln", log,1,0},
> {"sin", sin,1,0},
> {"cos", cos,1,0},
> {"tan", tan,1,0},
> {"asin", asin,1,0},
> {"acos", acos,1,0},
> {"atan", atan,1,0},
> {"atan2", FuncCast2(atan2),2,0},
> {"abs", fabs,1,0},
> {"sqrt", sqrt,1,0},
> {"pi", FuncCast0(pi),0,0},
> //...
> }
The code below uses no casting, and encapsulates the constructors
for 'formu_item's so that the functions are guaranteed to be in
sync with the discriminating member of the struct. The names and
types of members in formu_item have been changed slightly in some
cases, but except for that it should drop in to the existing code
pretty easily. The final function shows how to invoke a function
in the table safely.
I have added a few bits of running commentary.
Code compiles cleanly (if I haven't made any editing mistakes)
with -pedantic -Wall -Wextra, under c++11, c++14, and c++17.
union FuncU {
double (*zero)();
double (*one)( double );
double (*two)( double, double );
double (*three)( double, double, double );
constexpr FuncU( double (*f)() ) : zero( f ) {}
constexpr FuncU( double (*f)( double ) ) : one( f ) {}
constexpr FuncU( double (*f)( double, double ) ) : two( f ) {}
constexpr FuncU( double (*f)( double, double, double ) ) : three( f ) {}
// a member for each kind of function, and
// a constructor for each of the different function kinds
};
typedef enum {
A_ZERO, A_ONE, A_TWO, A_THREE,
// I use an enum rather than an int
} FKind;
struct formu_item {
const char *name;
FKind n_pars;
bool varying;
FuncU fu;
};
// next is the core idea - have a type-safe constructor
// for each of the different kinds of functions
constexpr formu_item
zero_f( const char *name, bool varying, double (*f)() ){
return { name, A_ZERO, varying, FuncU( f ) };
}
constexpr formu_item
one_f( const char *name, bool varying, double (*f)( double ) ){
return { name, A_ONE, varying, FuncU( f ) };
}
constexpr formu_item
two_f( const char *name, bool varying, double (*f)( double, double ) ){
return { name, A_TWO, varying, FuncU( f ) };
}
typedef double (*Fdouble3)( double, double, double );
constexpr formu_item
three_f( const char *name, bool varying, Fdouble3 f ){
return { name, A_THREE, varying, FuncU( f ) };
}
#include
static double pi(){
return 3.1415926535897932384626433832795029;
}
static const formu_item ftable_static[]= {
one_f( "exp", 0, exp ),
one_f( "ln", 0, log ),
one_f( "sin", 0, sin ),
one_f( "cos", 0, cos ),
one_f( "tan", 0, tan ),
one_f( "asin", 0, asin ),
one_f( "acos", 0, acos ),
one_f( "atan", 0, atan ),
two_f( "atan2", 0, atan2 ),
one_f( "abs", 0, fabs ),
one_f( "sqrt", 0, sqrt ),
zero_f( "pi", 0, pi ),
// the function table. Note that if the supplied function
// doesn't match the associated constructor then there will
// be a compilation error
};
double
invoke_formula( unsigned k, double a, double b, double c ){
unsigned n = sizeof ftable_static / sizeof ftable_static[0];
if( k >= n ) return 0. / 0.; // k too large => NaN
switch( ftable_static[k].n_pars ){
case A_ZERO: return ftable_static[k].fu.zero();
case A_ONE: return ftable_static[k].fu.one( a );
case A_TWO: return ftable_static[k].fu.two( a, b );
case A_THREE: return ftable_static[k].fu.three( a, b, c );
}
return -1. / 0.; // table messed up => infinity
}