Discussion:
Proper cast of function pointers
Add Reply
Paavo Helde
2024-04-23 11:31:43 UTC
Reply
Permalink
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use. C++
standard says this is kosher, and there have never been any problems
with actual behavior. Alas, different versions and compile modes of g++
still produce warnings. What would be the best way to silence them
(without just switching off warnings)?


Simplified example:

typedef double (*Func)(double);
typedef double (*DoubleFunc_2_args)(double, double);

Func FuncCast2(DoubleFunc_2_args fp) {
return reinterpret_cast<Func>(fp);
}

$ g++ test1.cpp -Wall -Wextra
test1.cpp: In function ‘double (* FuncCast2(DoubleFunc_2_args))(double)’:
test1.cpp:26:34: warning: cast between incompatible function types from
‘DoubleFunc_2_args’ {aka ‘double (*)(double, double)’} to ‘Func’ {aka
‘double (*)(double)’} [-Wcast-function-type]
return reinterpret_cast<Func>(fp);


If I change the function to use more indirection, then there is a
warning with -O2 only:

Func FuncCast2(DoubleFunc_2_args fp) {
return *reinterpret_cast<Func*>(&fp);
}

$ g++ test1.cpp -Wall -Wextra -O2
test1.cpp: In function ‘double (* FuncCast2(DoubleFunc_2_args))(double)’:
test1.cpp:22:10: warning: dereferencing type-punned pointer will break
strict-aliasing rules [-Wstrict-aliasing]
return *reinterpret_cast<Func*>(&fp);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~

$ g++ --version
g++ (Debian 10.2.1-6) 10.2.1 20210110
Bonita Montero
2024-04-23 11:44:30 UTC
Reply
Permalink
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use.
C++ standard says this is kosher, and there have never been any
problems with actual behavior. Alas, different versions and compile
modes of g++ still produce warnings. ...
That's because of the danger that someone calls the function-pointer
to which you cast. Maybe you can cast through a void-pointer to sup-
press this warning. But for me casting to a function pointer of a
differnt type doesn't make sense at at..
I think you confront yourself to uncertainties which actually never
happen.
Paavo Helde
2024-04-23 18:33:10 UTC
Reply
Permalink
Post by Bonita Montero
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use.
C++  standard says this is kosher, and there have never been any
problems  with actual behavior. Alas, different versions and compile
modes of g++ still produce warnings. ...
That's because of the danger that someone calls the function-pointer
to which you cast. Maybe you can cast through a void-pointer to sup-
press this warning. But for me casting to a function pointer of a
differnt type doesn't make sense at at..
I think you confront yourself to uncertainties which actually never
happen.
The function pointers are cast to a single type so that they can be
stored in a common lookup array. I could use a union there, but this
would mean additional work with no real benefit, as the hypothetical
"someone" could just as easily mess up the unions than the casting.
David Brown
2024-04-24 07:33:12 UTC
Reply
Permalink
Post by Paavo Helde
Post by Bonita Montero
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use.
C++  standard says this is kosher, and there have never been any
problems  with actual behavior. Alas, different versions and compile
modes of g++ still produce warnings. ...
That's because of the danger that someone calls the function-pointer
to which you cast. Maybe you can cast through a void-pointer to sup-
press this warning. But for me casting to a function pointer of a
differnt type doesn't make sense at at..
I think you confront yourself to uncertainties which actually never
happen.
The function pointers are cast to a single type so that they can be
stored in a common lookup array. I could use a union there, but this
would mean additional work with no real benefit, as the hypothetical
"someone" could just as easily mess up the unions than the casting.
You could consider using std::variant<>, but you need to know the
possible function types in advance (that may or may not be an issue for
you) and it stores an index to in the variant object. Again, that may
or may not be a useful thing for you.

Otherwise I agree with you that casting to a common "void (*)(void)"
pointer type seems reasonable here. Just disable the warning around the
casts.
Bonita Montero
2024-04-24 07:36:45 UTC
Reply
Permalink
Post by 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<>.
Paavo Helde
2024-04-24 12:32:36 UTC
Reply
Permalink
Post by Bonita Montero
Post by 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 <math.h>

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<Func>(reinterpret_cast<VoidFunc>(fp));
}
Func FuncCast0(DoubleFunc_0_args fp) {
return reinterpret_cast<Func>(reinterpret_cast<VoidFunc>(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},
//...
}
Tim Rentsch
2024-04-24 22:10:52 UTC
Reply
Permalink
Post by Paavo Helde
Post by Bonita Montero
Post by 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 <math.h>
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<Func>(reinterpret_cast<VoidFunc>(fp));
}
Func FuncCast0(DoubleFunc_0_args fp) {
return reinterpret_cast<Func>(reinterpret_cast<VoidFunc>(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 <math.h>

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
}
Chris M. Thomasson
2024-04-24 22:14:10 UTC
Reply
Permalink
Post by Paavo Helde
Post by Bonita Montero
Post by 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 <math.h>
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<Func>(reinterpret_cast<VoidFunc>(fp));
}
Func FuncCast0(DoubleFunc_0_args fp) {
return reinterpret_cast<Func>(reinterpret_cast<VoidFunc>(fp));
}
double pi() {
return 3.1415926535897932384626433832795029;
}
static const formu_item ftable_static[TABLESIZE]=
[...]

For some damn reason this makes me think of this older code:

https://groups.google.com/g/comp.lang.c/c/8X_an3MpDYQ/m/TYw70DcnBQAJ

https://pastebin.com/raw/f52a443b1
Paavo Helde
2024-04-25 05:37:07 UTC
Reply
Permalink
[...]
Post by Tim Rentsch
Post by Paavo Helde
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.
Indeed. Somehow I was convinced that when providing multiple
constructors, the compiler would fail with ambiguity errors because all
of these functions like sin() are overloaded in C++. But it seems the
compiler figures it out nicely.
Post by Tim Rentsch
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.
This is not exactly true, when compiling with VS2022 I get two compile
errors:

if (k >= n) return 0. / 0.; // k too large => NaN

1>C:\Test\ConsoleTestVS2022\ConsoleTest2022\main.cpp(97,18): error
C2124: divide or mod by zero

But that's fully another topic.

Thanks for the demo code!
BR
Tim Rentsch
2024-04-25 22:04:42 UTC
Reply
Permalink
[...]
Post by Tim Rentsch
Post by Paavo Helde
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.
Indeed. Somehow I was convinced that when providing multiple
constructors, the compiler would fail with ambiguity errors because
all of these functions like sin() are overloaded in C++. But it seems
the compiler figures it out nicely.
As it turns out I may have complicated the question by using
<math.h> rather than <cmath>. However, upon trying again with
<cmath> and with both <math.h> and <cmath> I learned that
overload resolution is indeed smart enough to figure out which
function matches. I expect this works because exact matches
always take precedence.
Post by Tim Rentsch
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.
This is not exactly true, when compiling with VS2022 I get two compile
if (k >= n) return 0. / 0.; // k too large => NaN
1>C:\Test\ConsoleTestVS2022\ConsoleTest2022\main.cpp(97,18): error
C2124: divide or mod by zero
How strange. In C it would work (and it does work under gcc
and clang). Apparently the C++ standard is fuzzier about what
is required for floating-point constant expressions.
But that's fully another topic.
Right. Also I expect the limitation is easy to get around, if
that is important (and here it really wasn't).
Thanks for the demo code!
You are most welcome. I am definitely a proponent of avoiding
casts whenever possible.
Bonita Montero
2024-04-25 09:19:03 UTC
Reply
Permalink
Wouldn't that be more convenient ?

static vector<pair<char const *, function<double ( double, double,
double )>>> fns;
fns.reserve( 12 );
fns.emplace_back( "exp", +[]( double num, double, double ) ->
double { return exp( num ); } );
fns.emplace_back( "ln", +[]( double num, double, double ) -> double
{ return log( num ); } );
fns.emplace_back( "sin", +[]( double num, double, double ) ->
double { return sin( num ); } );
fns.emplace_back( "cos", +[]( double num, double, double ) ->
double { return cos( num ); } );
fns.emplace_back( "tan", +[]( double num, double, double ) ->
double { return tan( num ); } );
fns.emplace_back( "asin", +[]( double num, double, double ) ->
double { return asin( num ); } );
fns.emplace_back( "acos", +[]( double num, double, double ) ->
double { return acos( num ); } );
fns.emplace_back( "atan", +[]( double num, double, double ) ->
double { return atan( num ); } );
fns.emplace_back( "atan2", +[]( double x, double y, double ) ->
double { return atan2( x, y ); } );
fns.emplace_back( "abs", +[]( double num, double, double ) ->
double { return abs( num ); } );
fns.emplace_back( "sqrt", +[]( double num, double, double ) ->
double { return sqrt( num ); } );
fns.emplace_back( "pi", +[]( double, double, double ) -> double {
return 3.14; } );
Bonita Montero
2024-04-25 09:39:33 UTC
Reply
Permalink
Post by Bonita Montero
Wouldn't that be more convenient ?
    static vector<pair<char const *, function<double ( double, double,
double )>>> fns;
    fns.reserve( 12 );
    fns.emplace_back( "exp", +[]( double num, double, double ) ->
double { return exp( num ); } );
    fns.emplace_back( "ln", +[]( double num, double, double ) -> double
{ return log( num ); } );
    fns.emplace_back( "sin", +[]( double num, double, double ) ->
double { return sin( num ); } );
    fns.emplace_back( "cos", +[]( double num, double, double ) ->
double { return cos( num ); } );
    fns.emplace_back( "tan", +[]( double num, double, double ) ->
double { return tan( num ); } );
    fns.emplace_back( "asin", +[]( double num, double, double ) ->
double { return asin( num ); } );
    fns.emplace_back( "acos", +[]( double num, double, double ) ->
double { return acos( num ); } );
    fns.emplace_back( "atan", +[]( double num, double, double ) ->
double { return atan( num ); } );
    fns.emplace_back( "atan2", +[]( double x, double y, double ) ->
double { return atan2( x, y ); } );
    fns.emplace_back( "abs", +[]( double num, double, double ) ->
double { return abs( num ); } );
    fns.emplace_back( "sqrt", +[]( double num, double, double ) ->
double { return sqrt( num ); } );
    fns.emplace_back( "pi", +[]( double, double, double ) -> double {
return 3.14; } );
Or somewhat simpler:

vector<pair<char const *, double (*)( double, double, double )>> fns;
fns.reserve( 12 );
fns.emplace_back( "exp", +[]( double num, double, double ) ->
double { return exp( num ); } );
fns.emplace_back( "ln", +[]( double num, double, double ) -> double
{ return log( num ); } );
fns.emplace_back( "sin", +[]( double num, double, double ) ->
double { return sin( num ); } );
fns.emplace_back( "cos", +[]( double num, double, double ) ->
double { return cos( num ); } );
fns.emplace_back( "tan", +[]( double num, double, double ) ->
double { return tan( num ); } );
fns.emplace_back( "asin", +[]( double num, double, double ) ->
double { return asin( num ); } );
fns.emplace_back( "acos", +[]( double num, double, double ) ->
double { return acos( num ); } );
fns.emplace_back( "atan", +[]( double num, double, double ) ->
double { return atan( num ); } );
fns.emplace_back( "atan2", +[]( double x, double y, double ) ->
double { return atan2( x, y ); } );
fns.emplace_back( "abs", +[]( double num, double, double ) ->
double { return abs( num ); } );
fns.emplace_back( "sqrt", +[]( double num, double, double ) ->
double { return sqrt( num ); } );
fns.emplace_back( "pi", +[]( double, double, double ) -> double {
return 3.14; } );
Bonita Montero
2024-04-25 09:48:12 UTC
Reply
Permalink
So this takes no CPU-time at all:

static pair<char const *, double (*)( double, double, double )>
const fns[] =
{
{ "exp", []( double num, double, double ) -> double { return
exp( num ); } },
{ "ln", []( double num, double, double ) -> double { return
log( num ); } },
{ "sin", []( double num, double, double ) -> double { return
sin( num ); } },
{ "cos", []( double num, double, double ) -> double { return
cos( num ); } },
{ "tan", []( double num, double, double ) -> double { return
tan( num ); } },
{ "asin", []( double num, double, double ) -> double { return
asin( num ); } },
{ "acos", []( double num, double, double ) -> double { return
acos( num ); } },
{ "atan", []( double num, double, double ) -> double { return
atan( num ); } },
{ "atan2", []( double x, double y, double ) -> double { return
atan2( x, y ); } },
{ "abs", []( double num, double, double ) -> double { return
abs( num ); } },
{ "sqrt", []( double num, double, double ) -> double { return
sqrt( num ); } },
{ "pi", []( double, double, double ) -> double { return 3.14; } }
};
Paavo Helde
2024-04-25 19:22:34 UTC
Reply
Permalink
    static pair<char const *, double (*)( double, double, double )>
const fns[] =
    {
        { "exp", []( double num, double, double ) -> double { return
exp( num ); } },
        { "ln", []( double num, double, double ) -> double { return
log( num ); } },
        { "sin", []( double num, double, double ) -> double { return
sin( num ); } },
        { "cos", []( double num, double, double ) -> double { return
cos( num ); } },
        { "tan", []( double num, double, double ) -> double { return
tan( num ); } },
        { "asin", []( double num, double, double ) -> double { return
asin( num ); } },
        { "acos", []( double num, double, double ) -> double { return
acos( num ); } },
        { "atan", []( double num, double, double ) -> double { return
atan( num ); } },
        { "atan2", []( double x, double y, double ) -> double { return
atan2( x, y ); } },
        { "abs", []( double num, double, double ) -> double { return
abs( num ); } },
        { "sqrt", []( double num, double, double ) -> double { return
sqrt( num ); } },
        { "pi", []( double, double, double ) -> double { return 3.14; } }
    };
Looks cleaner, but for using with the current code the correct number of
arguments would still be needed to stored separately, as this is the
only thing which tells the expression evaluator how many arguments to
pop out of the operand stack.

Current usage goes about like this:

switch(ftable[*rcode].n_pars) {
case 0:
*bufp++ = (*BackCast0(ftable[*rcode++].f))();
break;
case 1:
x = *--bufp;
*bufp++ = ftable[*rcode++].f(x);
break;
case 2:
y = *--bufp;
x = *--bufp;
*bufp++ = (*BackCast2(ftable[*rcode++].f))(x,y);
break;
case 3:
z = *--bufp;
y = *--bufp;
x = *--bufp;
*bufp++ = (*BackCast3(ftable[*rcode++].f))(x,y,z);
break;
}

BackCast* are just another function pointer casts, e.g.

DoubleFunc_2_args BackCast2(Func fp) {
return reinterpret_cast<DoubleFunc_2_args>(reinterpret_cast<VoidFunc>(fp));
}

With your proposal this code could be indeed shortened and there would
be no need for casts ;-)

switch(ftable[*rcode].n_pars) {
case 3:
z = *--bufp;
[[fallthrough]];
case 2:
y = *--bufp;
[[fallthrough]];
case 1:
x = *--bufp;
}
*bufp++ = ftable[*rcode++].f(x,y,z);
Bonita Montero
2024-04-26 05:52:15 UTC
Reply
Permalink
Post by Paavo Helde
     static pair<char const *, double (*)( double, double, double )>
const fns[] =
     {
         { "exp", []( double num, double, double ) -> double { return
exp( num ); } },
         { "ln", []( double num, double, double ) -> double { return
log( num ); } },
         { "sin", []( double num, double, double ) -> double { return
sin( num ); } },
         { "cos", []( double num, double, double ) -> double { return
cos( num ); } },
         { "tan", []( double num, double, double ) -> double { return
tan( num ); } },
         { "asin", []( double num, double, double ) -> double { return
asin( num ); } },
         { "acos", []( double num, double, double ) -> double { return
acos( num ); } },
         { "atan", []( double num, double, double ) -> double { return
atan( num ); } },
         { "atan2", []( double x, double y, double ) -> double {
return atan2( x, y ); } },
         { "abs", []( double num, double, double ) -> double { return
abs( num ); } },
         { "sqrt", []( double num, double, double ) -> double { return
sqrt( num ); } },
         { "pi", []( double, double, double ) -> double { return 3.14;
} }
     };
Looks cleaner, but for using with the current code the correct number of
arguments would still be needed to stored separately, ...
And how about that:

struct fn_t
{
char const *what;
size_t nParams;
double (*fn)( double, double, double );
};
static fn_t const fns[] =
{
{ "exp", 1, []( double num, double, double ) -> double { return exp(
num ); } },
{ "ln", 1, []( double num, double, double ) -> double { return log(
num ); } },
{ "sin", 1, []( double num, double, double ) -> double { return sin(
num ); } },
{ "cos", 1, []( double num, double, double ) -> double { return cos(
num ); } },
{ "tan", 1, []( double num, double, double ) -> double { return tan(
num ); } },
{ "asin", 1, []( double num, double, double ) -> double { return asin(
num ); } },
{ "acos", 1, []( double num, double, double ) -> double { return acos(
num ); } },
{ "atan", 1, []( double num, double, double ) -> double { return atan(
num ); } },
{ "atan2", 2, []( double x, double y, double ) -> double { return
atan2( x, y ); } },
{ "abs", 1, []( double num, double, double ) -> double { return abs(
num ); } },
{ "sqrt", 1, []( double num, double, double ) -> double { return sqrt(
num ); } },
{ "pi", 1, []( double, double, double ) -> double { return 3.14; } }
};

Tim Rentsch
2024-04-24 21:40:33 UTC
Reply
Permalink
Post by Paavo Helde
Post by Bonita Montero
Post by Paavo Helde
There is an old third-party library where some function pointers
are casted to another type, then back to the original type before
use.
C++ standard says this is kosher, and there have never been any
problems with actual behavior. Alas, different versions and compile
modes of g++ still produce warnings. ...
That's because of the danger that someone calls the function-pointer
to which you cast. Maybe you can cast through a void-pointer to sup-
press this warning. But for me casting to a function pointer of a
differnt type doesn't make sense at at..
I think you confront yourself to uncertainties which actually never
happen.
The function pointers are cast to a single type so that they can be
stored in a common lookup array. I could use a union there, but this
would mean additional work with no real benefit, as the hypothetical
"someone" could just as easily mess up the unions than the casting.
That depends on how the code is written. The code can be written
so that making a mistake with the unions is both difficult and
unlikely. To be continued downthread...
Markus Schaaf
2024-04-23 12:23:33 UTC
Reply
Permalink
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use. C++
standard says this is kosher, and there have never been any problems
with actual behavior. Alas, different versions and compile modes of g++
still produce warnings. What would be the best way to silence them
(without just switching off warnings)?
You could use a union, if you know all possible function types
beforehand.
Post by Paavo Helde
typedef double (*Func)(double);
typedef double (*DoubleFunc_2_args)(double, double);
Func FuncCast2(DoubleFunc_2_args fp) {
return reinterpret_cast<Func>(fp);
}
$ g++ test1.cpp -Wall -Wextra
test1.cpp:26:34: warning: cast between incompatible function types from
‘DoubleFunc_2_args’ {aka ‘double (*)(double, double)’} to ‘Func’ {aka
‘double (*)(double)’} [-Wcast-function-type]
return reinterpret_cast<Func>(fp);
You could wonder why you are asking for non-standard (extra)
warnings in the first place.

Out of curiosity I have fiddled with g++, asking myself if there
is a type like (void*) for objects, that the compiler is happy
converting function pointers into. And alas, there is! And it is
the type one would guess:

typedef void (*UniversalFunctionPointer)();

Of course it's a cat-and-mouse play with these compiler warnings.

BR
David Brown
2024-04-23 14:44:20 UTC
Reply
Permalink
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use. C++
standard says this is kosher, and there have never been any problems
with actual behavior. Alas, different versions and compile modes of g++
still produce warnings. What would be the best way to silence them
(without just switching off warnings)?
You could use a union, if you know all possible function types beforehand.
You /could/, if you don't mind the undefined behaviour - type-punning
unions are not defined behaviour in C++.
Post by Paavo Helde
typedef double (*Func)(double);
typedef double (*DoubleFunc_2_args)(double, double);
Func FuncCast2(DoubleFunc_2_args fp) {
    return reinterpret_cast<Func>(fp);
}
$ g++ test1.cpp -Wall -Wextra
test1.cpp:26:34: warning: cast between incompatible function types from
‘DoubleFunc_2_args’ {aka ‘double (*)(double, double)’} to ‘Func’ {aka
‘double (*)(double)’} [-Wcast-function-type]
    return reinterpret_cast<Func>(fp);
You could wonder why you are asking for non-standard (extra) warnings in
the first place.
I can't answer for the OP, but I know why /I/ use lots of extra warnings
in my code. (What do you mean by "non-standard warnings" anyway? There
are standards-required diagnostics, but AFAIK the standard does not
distinguish between errors and warnings, except for #error directives.)
Markus Schaaf
2024-04-23 15:00:39 UTC
Reply
Permalink
Post by David Brown
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use. C++
standard says this is kosher, and there have never been any problems
with actual behavior. Alas, different versions and compile modes of g++
still produce warnings. What would be the best way to silence them
(without just switching off warnings)?
You could use a union, if you know all possible function types beforehand.
You /could/, if you don't mind the undefined behaviour - type-punning
unions are not defined behaviour in C++.
I have no idea what you are writing about. Of course one would
read the exact same member of the union one had written to
before. That is what unions are for.

BR
David Brown
2024-04-24 07:55:36 UTC
Reply
Permalink
Post by David Brown
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use. C++
standard says this is kosher, and there have never been any problems
with actual behavior. Alas, different versions and compile modes of g++
still produce warnings. What would be the best way to silence them
(without just switching off warnings)?
You could use a union, if you know all possible function types beforehand.
You /could/, if you don't mind the undefined behaviour - type-punning
unions are not defined behaviour in C++.
I have no idea what you are writing about. Of course one would read the
exact same member of the union one had written to before. That is what
unions are for.
BR
Type-punning unions are defined behaviour in C, undefined in C++. A
typical example (written to be compilable as C and C++) would be :

typedef union RawFloat {
float f;
unsigned int u;
} RawFloat;

unsigned int float_to_raw(float f) {
RawFloat r;
r.f = f;
return r.u;
}

In C, writing to one union member and then reading via a different union
member is defined behaviour - you get the same underlying
representation, re-interpreted as the new type. This is known as "type
punning", and was explicitly given defined behaviour in C99. (Prior to
C99, the standard was vague on the topic.)

In C++, this is undefined behaviour - you may not read a union member
that was not the last written member.

Many - perhaps most - C++ compilers allow type-punning via unions in
C++, as long as they are standard layout unions (simple types with no
constructors, virtual functions, or anything beyond plain C). But this
is not defined behaviour according to the C++ standards. In C++, the
defined ways to achieve type punning are std::memcpy and std::bit_cast<>
(in C++20).


Unions were /not/ intended for type-punning. They were designed for
saving memory and to support "sum" types (compared to structs which are
"product" types). But they are also sometimes used for type-punning,
and the definition of this behaviour was added to C99 because some
people non-portably relied on that behaviour in existing C code. It was
/not/ added to C++ because specifying such a rule would be complicated
in the face of more advanced types.


<https://en.cppreference.com/w/c/language/union>
(See the first "since C99" box)

<https://en.cppreference.com/w/cpp/language/union>
(See the first paragraph of "Explanation")
Bonita Montero
2024-04-24 11:11:08 UTC
Reply
Permalink
Post by David Brown
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use. C++
standard says this is kosher, and there have never been any problems
with actual behavior. Alas, different versions and compile modes of g++
still produce warnings. What would be the best way to silence them
(without just switching off warnings)?
You could use a union, if you know all possible function types beforehand.
You /could/, if you don't mind the undefined behaviour - type-punning
unions are not defined behaviour in C++.
I have no idea what you are writing about. Of course one would read the
exact same member of the union one had written to before. That is what
unions are for.
A union actually isn't needed for the discusses purpose.
Just do a reinterpret_cast or a C-style cast.
David Brown
2024-04-24 11:15:40 UTC
Reply
Permalink
Post by Bonita Montero
Post by David Brown
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use. C++
standard says this is kosher, and there have never been any problems
with actual behavior. Alas, different versions and compile modes of g++
still produce warnings. What would be the best way to silence them
(without just switching off warnings)?
You could use a union, if you know all possible function types beforehand.
You /could/, if you don't mind the undefined behaviour - type-punning
unions are not defined behaviour in C++.
I have no idea what you are writing about. Of course one would read
the exact same member of the union one had written to before. That is
what unions are for.
A union actually isn't needed for the discusses purpose.
Just do a reinterpret_cast or a C-style cast.
Sure - that's what the OP is doing. It's not the cast that is his
problem - it is the compiler warning that he'd like to avoid.
Bonita Montero
2024-04-24 11:10:17 UTC
Reply
Permalink
Post by David Brown
You /could/, if you don't mind the undefined behaviour - type-punning
unions are not defined behaviour in C++.
Actually all compiler support that. But MSVC++ isn't that efficient
with that like clang or g++; MSVC often does a store and load round-
trip. But there's bit_cast with C++20, which also works efficient
with MSVC++. But actually he neither needs bit_cast nor unions for
his purpose, but just a reinterpret_cast or a C-style cast, which
I prefer for readability.
David Brown
2024-04-24 11:23:33 UTC
Reply
Permalink
Post by Bonita Montero
Post by David Brown
You /could/, if you don't mind the undefined behaviour - type-punning
unions are not defined behaviour in C++.
Actually all compiler support that.
That may be true - but I am entirely confident that you don't know it is
true. You live in a little world where the only compiler is MSVC++ -
you know nothing about the hundred other C++ compilers out there. (I
know more of them than you - and more importantly, I know my knowledge
is limited.)

Compiler extensions can be useful - and sometimes essential. It's fine
to write non-portable code when you have to do so. It is a much worse
idea to use non-portable code when it is needless to do so. There's no
need to use type-punning unions in C++, so don't do it - even if it
happens to work on the compilers you tested.
Post by Bonita Montero
But MSVC++ isn't that efficient
with that like clang or g++; MSVC often does a store and load round-
trip. But there's bit_cast with C++20, which also works efficient
with MSVC++.
Yes, std::bit_cast<> is (as I said) a good and well-defined alternative
to type-punning unions in C++20. And if you have to use a poor-quality
compiler that can't do a decent job of optimising memcpy, then it's
definitely the way to go for general type-punning uses.
Post by Bonita Montero
But actually he neither needs bit_cast nor unions for
his purpose, but just a reinterpret_cast or a C-style cast, which
I prefer for readability.
Agreed, in this particular case - and reinterpret_cast<> was the OP's
original choice.
Bonita Montero
2024-04-24 15:06:56 UTC
Reply
Permalink
Post by David Brown
That may be true - but I am entirely confident that you don't know it is
true.  You live in a little world where the only compiler is MSVC++ -
you know nothing about the hundred other C++ compilers out there.  (I
know more of them than you - and more importantly, I know my knowledge
is limited.)
Any C++ compiler supports that since this is very common and you
coudln't port a lot of code to compilers who wouldn't understand
that. I've no problem using a union since this feature is required
for any compiler to be conformant with a lot of software.
David Brown
2024-04-24 15:59:25 UTC
Reply
Permalink
Post by Bonita Montero
Post by David Brown
That may be true - but I am entirely confident that you don't know it
is true.  You live in a little world where the only compiler is MSVC++
- you know nothing about the hundred other C++ compilers out there.
(I know more of them than you - and more importantly, I know my
knowledge is limited.)
Any C++ compiler supports that since this is very common
That is clearly wrong. I am reasonably confident that my ancient copy
of a Microtek C++ compiler for the 68k supported type-punning in
practice (not by design, but simply because it did little in the way of
optimisation) - that does not imply that the compiler was very common.

What you are trying to say is that the most common C++ compilers support
it. I believe that is true, but I am not sure if they all /document/
that they support it - and if they don't document it as a guaranteed
feature, you are relying on luck. Can you point to where MSVC++
documents that type-punning unions are supported in C++?
Post by Bonita Montero
and you
coudln't port a lot of code to compilers who wouldn't understand
that. I've no problem using a union since this feature is required
for any compiler to be conformant with a lot of software.
It is certainly the case that a large proportion of code is
non-portable, and uses extensions or platform-specific features. Mostly
it is for good reason - you are using Linux system calls, or Windows API
functions, or need MSVC "cdecl" or gcc "__attribute__" for some purpose.
That's fine.

What is /not/ fine, in my book, is /pointless/ non-portability.

I think it is quite uncommon to do type-punning using unions - type
punning is very rarely needed, no matter how it is done. And even if
some lazy or ignorant programmers do so, there is no good reason for
/you/ to do it or recommend it. You are no longer ignorant about the
issue - you now know it is non-portable. And surely you are not
suggesting that it is a good thing to be lazy rather than using
well-defined code?
Bonita Montero
2024-04-24 17:36:10 UTC
Reply
Permalink
That is clearly wrong.  I am reasonably confident that my ancient
copy of a Microtek C++ compiler for the 68k supported type-punning in
practice (not by design, but simply because it did little in the way of
optimisation) - that does not imply that the compiler was very common.
It's a common pattern for decades and because of that any compiler does
support that.

Rest unread
Paavo Helde
2024-04-23 18:50:44 UTC
Reply
Permalink
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use. C++
standard says this is kosher, and there have never been any problems
with actual behavior. Alas, different versions and compile modes of g++
still produce warnings. What would be the best way to silence them
(without just switching off warnings)?
You could use a union, if you know all possible function types beforehand.
Right, I could use union, it's just extra work and some danger of
introducing new bugs in this old third-party code.
Post by Paavo Helde
typedef double (*Func)(double);
typedef double (*DoubleFunc_2_args)(double, double);
Func FuncCast2(DoubleFunc_2_args fp) {
    return reinterpret_cast<Func>(fp);
}
$ g++ test1.cpp -Wall -Wextra
test1.cpp:26:34: warning: cast between incompatible function types from
‘DoubleFunc_2_args’ {aka ‘double (*)(double, double)’} to ‘Func’ {aka
‘double (*)(double)’} [-Wcast-function-type]
    return reinterpret_cast<Func>(fp);
You could wonder why you are asking for non-standard (extra) warnings in
the first place.
Because using -Wall -Wextra (and sometimes more) has been encouraged by
people in this group and elsewhere.

I suspect -Wcast-function-type has been added into -Wextra during some
last 10 years, I'm pretty sure earlier this code used to compile without
warnings, it has been relatively recently when this has started to
irritate me (mostly because we have managed to get the rest of the
codebase almost warning-free ;-).
Out of curiosity I have fiddled with g++, asking myself if there is a
type like (void*) for objects, that the compiler is happy converting
function pointers into. And alas, there is! And it is the type one would
typedef void (*UniversalFunctionPointer)();
Adding an intermediate reinterpret_cast to this type indeed seems to get
rid of the warning! Thanks a lot!
Markus Schaaf
2024-04-23 20:18:49 UTC
Reply
Permalink
Post by Paavo Helde
Post by Markus Schaaf
typedef void (*UniversalFunctionPointer)();
Adding an intermediate reinterpret_cast to this type indeed seems to get
rid of the warning! Thanks a lot!
Wouldn't it be better to change the storage type of these
function pointers in your registry to above type, making the
intention clear to those you recognize its special property?
Instead of introducing obscure cast chains everywhere.

BR
Markus Schaaf
2024-04-23 20:22:29 UTC
Reply
Permalink
Post by Markus Schaaf
intention clear to those you recognize its special property?
... to those who recognize ...

(Phonetic typing error, interesting.)

BR
Paavo Helde
2024-04-23 20:33:30 UTC
Reply
Permalink
Post by Paavo Helde
Post by Markus Schaaf
typedef void (*UniversalFunctionPointer)();
Adding an intermediate reinterpret_cast to this type indeed seems to get
rid of the warning! Thanks a lot!
Wouldn't it be better to change the storage type of these function
pointers in your registry to above type, making the intention clear to
those you recognize its special property? Instead of introducing obscure
cast chains everywhere.
Yes, it would be better or at least more symmetric, assuming that
somebody will read this third-party code some day. This has not happened
in the last 20 years, but you never know. I will think about that.

BR
David Brown
2024-04-23 14:44:16 UTC
Reply
Permalink
Post by Paavo Helde
There is an old third-party library where some function pointers are
casted to another type, then back to the original type before use. C++
standard says this is kosher, and there have never been any problems
with actual behavior. Alas, different versions and compile modes of g++
still produce warnings. What would be the best way to silence them
(without just switching off warnings)?
typedef double (*Func)(double);
typedef double (*DoubleFunc_2_args)(double, double);
Func FuncCast2(DoubleFunc_2_args fp) {
    return reinterpret_cast<Func>(fp);
}
$ g++ test1.cpp -Wall -Wextra
test1.cpp:26:34: warning: cast between incompatible function types from
‘DoubleFunc_2_args’ {aka ‘double (*)(double, double)’} to ‘Func’ {aka
‘double (*)(double)’} [-Wcast-function-type]
  return reinterpret_cast<Func>(fp);
If I change the function to use more indirection, then there is a
Func FuncCast2(DoubleFunc_2_args fp) {
    return *reinterpret_cast<Func*>(&fp);
}
$ g++ test1.cpp -Wall -Wextra -O2
test1.cpp:22:10: warning: dereferencing type-punned pointer will break
strict-aliasing rules [-Wstrict-aliasing]
  return *reinterpret_cast<Func*>(&fp);
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ g++ --version
g++ (Debian 10.2.1-6) 10.2.1 20210110
The compiler is warning here about casting incompatible function types
because usually such casts are a bad idea. In particular, casting to a
different function type, then calling through this new type, is
undefined behaviour. About the only useful thing you can do with your
cast pointer is cast it back to the original type before calling it. So
g++ with -Wextra warns you that you have either made a mistake in your
code, or are trying to do something that is potentially dangerous.

The compiler then helpfully tells you exactly why you got the warning,
and makes it obvious what you can do to hide the warning, by giving you
the specific flag - "-Wcast-function-type". You disable that warning by
using "-Wno-cast-function-type", or by using the appropriate pragma:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
inline Func FuncCast2(DoubleFunc_2_args fp) {
return reinterpret_cast<Func>(fp);
}
#pragma GCC diagnostic pop
Bonita Montero
2024-04-24 09:27:26 UTC
Reply
Permalink
Post by David Brown
The compiler is warning here about casting incompatible function types
because usually such casts are a bad idea.  In particular, casting to
a different function type, then calling through this new type, is
undefined behaviour.  About the only useful thing you can do with your
cast pointer is cast it back to the original type before calling it.
So g++ with -Wextra warns you that you have either made a mistake in
your code, or are trying to do something that is potentially dangerous.
He needs to store different function-pointers with a common type;
why not as a void-pointer ?
David Brown
2024-04-24 11:14:24 UTC
Reply
Permalink
Post by Bonita Montero
Post by David Brown
The compiler is warning here about casting incompatible function types
because usually such casts are a bad idea.  In particular, casting to
a  different function type, then calling through this new type, is
undefined behaviour.  About the only useful thing you can do with your
cast pointer is cast it back to the original type before calling it.
So  g++ with -Wextra warns you that you have either made a mistake in
your code, or are trying to do something that is potentially dangerous.
He needs to store different function-pointers with a common type;
why not as a void-pointer ?
As far as I know, the standards do not define the behaviour of casting
between function pointers and void pointers (or any object pointers).
The C standards are clear about the matter - it is not defined
behaviour. But I don't know the C++ standards well enough to say.

For C, you can happily use "void (*)(void)" as a universal function
pointer type, rather than "* void" which is only for object pointers.

The compiler warning here is a good idea for most code, but sometimes
you need code that is potentially risky but you know is correct - such
as converting function pointer types before storing them, and converting
them back before using them. The OP just needs to disable the warning
around the code that does this conversion.
Bo Persson
2024-04-24 12:00:21 UTC
Reply
Permalink
Post by David Brown
Post by Bonita Montero
Post by David Brown
The compiler is warning here about casting incompatible function
types because usually such casts are a bad idea.  In particular,
casting to
a  different function type, then calling through this new type, is
undefined behaviour.  About the only useful thing you can do with
your cast pointer is cast it back to the original type before calling
it.
So  g++ with -Wextra warns you that you have either made a mistake in
your code, or are trying to do something that is potentially dangerous.
He needs to store different function-pointers with a common type;
why not as a void-pointer ?
As far as I know, the standards do not define the behaviour of casting
between function pointers and void pointers (or any object pointers).
The C standards are clear about the matter - it is not defined
behaviour.  But I don't know the C++ standards well enough to say.
This is something about theory and practice. A function pointer is
allowed to be larger than a void*.

In practice both Linux/Posix and Windows will use void* for returning
function pointers into dynamic libraries. So on those systems it will
obviously work.
David Brown
2024-04-24 14:41:02 UTC
Reply
Permalink
Post by Bo Persson
Post by David Brown
Post by Bonita Montero
Post by David Brown
The compiler is warning here about casting incompatible function
types because usually such casts are a bad idea.  In particular,
casting to
a  different function type, then calling through this new type, is
undefined behaviour.  About the only useful thing you can do with
your cast pointer is cast it back to the original type before
calling it.
So  g++ with -Wextra warns you that you have either made a mistake in
your code, or are trying to do something that is potentially dangerous.
He needs to store different function-pointers with a common type;
why not as a void-pointer ?
As far as I know, the standards do not define the behaviour of casting
between function pointers and void pointers (or any object pointers).
The C standards are clear about the matter - it is not defined
behaviour.  But I don't know the C++ standards well enough to say.
This is something about theory and practice. A function pointer is
allowed to be larger than a void*.
In practice both Linux/Posix and Windows will use void* for returning
function pointers into dynamic libraries. So on those systems it will
obviously work.
Sure. But I saw nothing in the OP's posts to indicate that he was using
POSIX or Windows. And it is silly to use "void *" pointers for generic
function pointers when "void (*)(void)" will work be design, rather than
luck, and is in no way more difficult or less efficient. (Note that
this is what the OP is already doing.)
Bonita Montero
2024-04-24 15:07:51 UTC
Reply
Permalink
Post by David Brown
As far as I know, the standards do not define the behaviour of casting
between function pointers and void pointers (or any object pointers).
The C standards are clear about the matter - it is not defined
behaviour.  But I don't know the C++ standards well enough to say.
There's for sure no compiler which doesn't support that.
David Brown
2024-04-24 16:02:26 UTC
Reply
Permalink
Post by Bonita Montero
Post by David Brown
As far as I know, the standards do not define the behaviour of casting
between function pointers and void pointers (or any object pointers).
The C standards are clear about the matter - it is not defined
behaviour.  But I don't know the C++ standards well enough to say.
There's for sure no compiler which doesn't support that.
Incorrect.

On the AVR (which is supported by gcc), code pointers can have different
sizes from data pointers. The same goes for the msp430, depending on
the memory model you use. The same goes for DOS compilers - if you have
a memory model with 16-bit data pointers and 32-bit code pointers, they
are not interchangeable.

The C and C++ standards don't restrict this kind of thing just to be
awkward - they do so to make it clear that such mixing may be
non-portable, and is completely unnecessary in coding.
Bonita Montero
2024-04-24 17:36:57 UTC
Reply
Permalink
Post by David Brown
On the AVR (which is supported by gcc), code pointers can have different
sizes from data pointers. ...
No one aliases code through a union.
David Brown
2024-04-24 19:18:27 UTC
Reply
Permalink
Post by Bonita Montero
Post by David Brown
On the AVR (which is supported by gcc), code pointers can have
different sizes from data pointers. ...
No one aliases code through a union.
What are you talking about?

The point here was that function pointers and object pointers do not
have standards-defined conversion behaviour (that fact is indisputable).
You claimed that all compilers support it anyway - I gave a few
examples where it could be problematic. (As usual, your idea of "all
compilers" is that only modern MSVC is important, with perhaps a brief
nod to the existence of gcc and clang, only on x86-64.)

Discussions with you are always difficult - you haven't any idea what
you are talking about, beyond the world of x86-64 programming with MSVC
on Windows. But that never stops you making wild claims and
generalisations, then changing the goalposts whenever challenged.

So if you have something useful to say, or questions to ask, go ahead.
If you just want to spout nonsense about what "all compilers" do, and
recommend pointlessly non-portable code instead of easy, portable and
well-defined alternatives, please don't bother.
Bonita Montero
2024-04-25 07:50:34 UTC
Reply
Permalink
Post by David Brown
The point here was that function pointers and object pointers do not
have standards-defined conversion behaviour (that fact is indisputable).
But you can alias other type-combinations for sure.
Loading...