• Proper cast of function pointers

    From Paavo Helde@21:1/5 to All on Tue Apr 23 14:31:43 2024
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Markus Schaaf@21:1/5 to All on Tue Apr 23 14:23:33 2024
    Am 23.04.24 um 13:31 schrieb 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.

    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);

    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Markus Schaaf on Tue Apr 23 16:44:20 2024
    On 23/04/2024 14:23, Markus Schaaf wrote:
    Am 23.04.24 um 13:31 schrieb 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++.


    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);

    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.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Markus Schaaf@21:1/5 to All on Tue Apr 23 17:00:39 2024
    Am 23.04.24 um 16:44 schrieb David Brown:
    On 23/04/2024 14:23, Markus Schaaf wrote:
    Am 23.04.24 um 13:31 schrieb 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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Paavo Helde on Tue Apr 23 16:44:16 2024
    On 23/04/2024 13:31, Paavo Helde wrote:

    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


    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paavo Helde@21:1/5 to All on Tue Apr 23 21:33:10 2024
    23.04.2024 14:44 Bonita Montero kirjutas:
    Am 23.04.2024 um 13:31 schrieb 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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paavo Helde@21:1/5 to All on Tue Apr 23 21:50:44 2024
    23.04.2024 15:23 Markus Schaaf kirjutas:
    Am 23.04.24 um 13:31 schrieb 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.


    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);

    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 guess:

    typedef void (*UniversalFunctionPointer)();

    Adding an intermediate reinterpret_cast to this type indeed seems to get
    rid of the warning! Thanks a lot!

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Markus Schaaf@21:1/5 to All on Tue Apr 23 22:18:49 2024
    Am 23.04.24 um 20:50 schrieb Paavo Helde:

    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paavo Helde@21:1/5 to All on Tue Apr 23 23:33:30 2024
    23.04.2024 23:18 Markus Schaaf kirjutas:
    Am 23.04.24 um 20:50 schrieb Paavo Helde:

    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Markus Schaaf@21:1/5 to All on Tue Apr 23 22:22:29 2024
    Am 23.04.24 um 22:18 schrieb Markus Schaaf:

    intention clear to those you recognize its special property?

    ... to those who recognize ...

    (Phonetic typing error, interesting.)

    BR

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Paavo Helde on Wed Apr 24 09:33:12 2024
    On 23/04/2024 20:33, Paavo Helde wrote:
    23.04.2024 14:44 Bonita Montero kirjutas:
    Am 23.04.2024 um 13:31 schrieb 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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Markus Schaaf on Wed Apr 24 09:55:36 2024
    On 23/04/2024 17:00, Markus Schaaf wrote:
    Am 23.04.24 um 16:44 schrieb David Brown:
    On 23/04/2024 14:23, Markus Schaaf wrote:
    Am 23.04.24 um 13:31 schrieb 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")

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Bonita Montero on Wed Apr 24 13:14:24 2024
    On 24/04/2024 11:27, Bonita Montero wrote:
    Am 23.04.2024 um 16:44 schrieb 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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Bo Persson@21:1/5 to David Brown on Wed Apr 24 14:00:21 2024
    On 2024-04-24 at 13:14, David Brown wrote:
    On 24/04/2024 11:27, Bonita Montero wrote:
    Am 23.04.2024 um 16:44 schrieb 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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Bonita Montero on Wed Apr 24 13:23:33 2024
    On 24/04/2024 13:10, Bonita Montero wrote:
    Am 23.04.2024 um 16:44 schrieb 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.

    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.

    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Bonita Montero on Wed Apr 24 13:15:40 2024
    On 24/04/2024 13:11, Bonita Montero wrote:
    Am 23.04.2024 um 17:00 schrieb Markus Schaaf:
    Am 23.04.24 um 16:44 schrieb David Brown:
    On 23/04/2024 14:23, Markus Schaaf wrote:
    Am 23.04.24 um 13:31 schrieb 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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paavo Helde@21:1/5 to All on Wed Apr 24 15:32:36 2024
    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 <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},
    //...
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Bo Persson on Wed Apr 24 16:41:02 2024
    On 24/04/2024 14:00, Bo Persson wrote:
    On 2024-04-24 at 13:14, David Brown wrote:
    On 24/04/2024 11:27, Bonita Montero wrote:
    Am 23.04.2024 um 16:44 schrieb 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.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Bonita Montero on Wed Apr 24 18:02:26 2024
    On 24/04/2024 17:07, Bonita Montero wrote:
    Am 24.04.2024 um 13:14 schrieb 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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Bonita Montero on Wed Apr 24 17:59:25 2024
    On 24/04/2024 17:06, Bonita Montero wrote:
    Am 24.04.2024 um 13:23 schrieb 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++?

    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?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Bonita Montero on Wed Apr 24 21:18:27 2024
    On 24/04/2024 19:36, Bonita Montero wrote:
    Am 24.04.2024 um 18:02 schrieb 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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Paavo Helde on Wed Apr 24 14:40:33 2024
    Paavo Helde <eesnimi@osa.pri.ee> writes:

    23.04.2024 14:44 Bonita Montero kirjutas:

    Am 23.04.2024 um 13:31 schrieb 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...

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Paavo Helde on Wed Apr 24 15:10:52 2024
    Paavo Helde <eesnimi@osa.pri.ee> 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 <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
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paavo Helde@21:1/5 to All on Thu Apr 25 08:37:07 2024
    25.04.2024 01:10 Tim Rentsch kirjutas:
    Paavo Helde <eesnimi@osa.pri.ee> writes:

    [...]
    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.


    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

    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paavo Helde@21:1/5 to All on Thu Apr 25 22:22:34 2024
    25.04.2024 12:48 Bonita Montero kirjutas:
    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; } }
        };

    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);

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Paavo Helde on Thu Apr 25 15:04:42 2024
    Paavo Helde <eesnimi@osa.pri.ee> writes:

    25.04.2024 01:10 Tim Rentsch kirjutas:

    Paavo Helde <eesnimi@osa.pri.ee> writes:

    [...]

    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.

    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

    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)