3

How can I set a function pointer depending on some condition to functions with different signature?

Example:

short int A() 
{
    return 0;
}

long int B()
{
    return 0;
}

void main()
{
   std::function<short int()> f = A;
   f();
   if(true)
   {
      //error
      f = B;
   }
}

How can use the same function pointer for two functions with different signature? Is it possible?

If is not, there is an efficient way to call the appropriate function depending on behavior instead of use a variable and split the whole code with if statements?

EDIT / EXPANSION ("2nd case")

#include <SDL.h>

class Obj { //whatever ...}

class A 
{
private:
    Uint16 ret16() { return SDL_ReadLE16(_pFile); }
    Uint32 ret32() { return SDL_ReadLE32(_pFile); }
    _pFile = nullptr;
public:
    Obj* func() 
    {
        Obj obj = new Obj();
        _pFile = SDL_RWFromFile("filename.bin","r"));
        auto ret = std::mem_fn(&SHPfile::ret16); 
        if(true)
        {
            ret = std::mem_fn(&SHPfile::ret32);          
        }

        //ret();
        // continue whatever
        // ....
        SDL_RWclose(_pFile);
        return *obj;
    }
}

I have a compilation error on a similar case using the Uint16 and Uint32 variable of SDL 2 library, using std::mem_fn

the compiler give me this error (relative to my code, but it's implemented in a way like the above example):

error: no match for ‘operator=’ (operand types are ‘std::_Mem_fn<short unsigned int (IO::File::*)()>’ and ‘std::_Mem_fn<unsigned int (IO::File::*)()>’)

To resolve this compilation error, I forced both the function to return a int type.

Is there a better way?

Or I did something wrong?

Raffaello
  • 1,641
  • 15
  • 29
  • Is it possible with "wrapping" the function and forcing to returning the same type, in this case the biggest one long int. but is there a better way? – Raffaello Jan 23 '15 at 22:46
  • 1
    Use a union or something like that? – Thomas Eding Jan 23 '15 at 22:49
  • 1
    Which compiler are you using? I just tested your code on `Clang LLVM 6` and it compiled just fine. – glampert Jan 24 '15 at 04:08
  • I am usign gcc 4.8. It's interesting to know Clang compile it. – Raffaello Jan 24 '15 at 09:26
  • @Ruffaello, I'm not sure if Clang's behaviour is standard or it is being allowed by a compiler extension... – glampert Jan 24 '15 at 17:23
  • 1
    clang,g++,VS compiles this fine (except for void main) – marcinj Jan 27 '15 at 10:04
  • It probably has much more to do with the stdlib being used than the compiler being used. – Puppy Jan 27 '15 at 10:55
  • @ThomasEding - Just want to thank you for the suggestion! I've 'stolen' your idea and posted it as an answer, here: https://stackoverflow.com/questions/57973305/what-s-the-best-way-to-cast-a-function-pointer-from-one-type-to-another/57977457#57977457 … But, please, feel free to re-post it yourself, so I can give you the due credit. – Adrian Mole Sep 17 '19 at 15:25

3 Answers3

4

The comments already say that clang accepts the code as is, and I can now say that GCC 4.8.4 and GCC 4.9.2 both accept it as well, after fixing void main() to say int main().

This use of std::function is perfectly valid. The C++11 standard says:

20.8.11.2 Class template function [func.wrap.func]

function& operator=(const function&);
function& operator=(function&&);
function& operator=(nullptr_t);

There is no template assignment operator here, so assignment of B could only construct a new temporary function<short int()> object, and move-assign from that. To determine whether the construction of that temporary is possible:

20.8.11.2.1 function construct/copy/destroy [func.wrap.func.con]

template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

7 Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R. The copy constructor and destructor of A shall not throw exceptions.

20.8.11.2 Class template function [func.wrap.func]

2 A callable object f of type F is Callable for argument types ArgTypes and return type R if the expression INVOKE(f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well formed (20.8.2).

20.8.2 Requirements [func.require]

2 Define INVOKE(f, t1, t2, ..., tN, R) as INVOKE(f, t1, t2, ..., tN) implicitly converted to R.

1 Define INVOKE(f, t1, t2, ..., tN) as follows:

  • ... (all related to pointer-to-member types)
  • f(t1, t2, ..., tN) in all other cases.

In short, this means that std::function<short int()> can be used with any function that can be called with no arguments, and which has a return type that can be implicitly converted to short. long clearly can be implicitly converted to short, so there is no problem whatsoever.

If your compiler's library doesn't accept it, and you cannot upgrade to a more recent version, one alternative is to try boost::function instead.

Aaron McDaid points out lambdas as another alternative: if your library's std::function is lacking, you can write

std::function<short int()> f = A;
f = []() -> short int { return B(); };

but if you take this route, you can take it a step further and avoid std::function altogether:

short int (*f)() = A;
f = []() -> short int { return B(); };

This works because lambas that don't capture anything are implicitly convertible to a pointer-to-function type that matches the lambda's arguments and return type. Effectively, it's short for writing

short int B_wrapper() { return B(); }
...
f = B_wrapper;

Note: the conversion from long to short may lose data. If you want to avoid that, you can use std::function<long int()> or long int (*)() instead.

Community
  • 1
  • 1
  • 1
    alternatively, if there's still a problem, use a lambda to convert the return type more explicitly. `f = []() -> short int {return B();};` – Aaron McDaid Jan 27 '15 at 10:36
  • 1
    @AaronMcDaid Thanks, and that allows for even further improvements. –  Jan 27 '15 at 10:52
  • That's a good answer, however I have to improve my question, because the example is simplified. and just to be sure: is this the case using the compiler flag for c++11? `g++ -std=c++11` working with that? – Raffaello Jan 29 '15 at 20:06
  • 1
    @Raffaello Yes, I tested with `-std=c++11` before answering. Without `-std=c++11` or better, `std::function` isn't available. (In C++03 mode, there is `std::tr1::function` in `` though, with which it also works, but then you can't have lambdas.) –  Jan 29 '15 at 21:33
  • @hvd Is there are reason to use int main instead of void? why is it required main have to return a int? – Raffaello Jan 31 '15 at 17:26
  • 1
    @Raffaello GCC issues a hard error if a C++ file declares `main` as returning `void` ("error: ‘::main’ must return ‘int’"), so that's a good reason for not doing that. GCC treats it as an error because the standard specifies it to be an error. In C, an incompatible declaration of `main` has undefined behaviour, so an implementation can silently accept it and let you get away with it, but C++ requires a diagnostic, and if the GCC folks have to issue a diagnostic anyway, and there's no good reason for ever writing `void main()` with GCC, then it makes sense to make the diagnostic an error. –  Jan 31 '15 at 17:36
0

No, you can't do that in a statically typed language unless your types all have a common super type, and C++ doesn't have that for primitives. You would need to box them into an object, then have the function return the object.

However, if you did that, you may as well just keep an object pointer around and use that instead of a function pointer, especially since it's going to make it easier to actually do something useful with the result without doing casts all over the place.

For example, in a calculator I wrote in Java, I wanted to work with BigInteger fractions as much as possible to preserve precision, but fallback to doubles for operations that returned irrational numbers. I created a Result interface, with BigFractionResult and DoubleResult implementations. The UI code would call things like Result sum = firstOperand.add(otherOperand) and didn't have to care which implementation of add it was using.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
  • This is wrong, the code is perfectly valid. It's a reasonable assumption that in a statically typed language, this wouldn't be possible, but C++ templates are powerful enough that it really is possible. –  Jan 27 '15 at 09:53
  • The code is only valid if you throw away the type information of the pointed-to function. The OP specifically said he didn't want everything coerced to a long int. My answer took that particular restriction into account. – Karl Bielefeldt Jan 28 '15 at 03:29
  • That's not what the OP said. Quoting the OP: "Is it possible with "wrapping" the function and forcing to returning the same type, in this case the biggest one long int. but is there a better way?" This disallows `long int A_wrapper() { return A(); }` as an answer, but there's nothing in there about otherwise preventing conversions of `A`'s result to `long`. –  Jan 28 '15 at 08:56
-1

The cleanest option that comes to mind is templates:

#include <iostream>
using namespace std;

template <typename T>
T foo() {
    return 0;
}

int main() {
    long a = foo<long>();
    cout << sizeof a << " bytes with value " << a << endl;

    int b = foo<int>();
    cout << sizeof b << " bytes with value " << b << endl;

    short c = foo<short>();
    cout << sizeof c << " bytes with value " << c << endl;

    return 0;
}

In ideone.com this outputs:

4 bytes with value 0
4 bytes with value 0
2 bytes with value 0

Hopefully this is what you needed.


If for some reason you really need to pass an actual function around, I would recommend looking into std::function and trying to write some template code using that.

Ixrec
  • 966
  • 1
  • 7
  • 20