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 }