function pointers and return type conversions

2019-06-22 12:13发布

问题:

Suppose I have a function that performs some side effect and then returns an answer:

int foo()
{
    perform_some_side_effect();
    return 42;
}

I want to bind foo to a function pointer, but I'm not interested in the answer, just the side effect:

void (*bar)() = foo;

However, this appears to be a type error:

error: invalid conversion from ‘int (*)()’ to ‘void (*)()’

What is the rationale behind that error? Why doesn't the type system allow me to ignore the answer?


On a side note, it works if I wrap the function pointer in a std::function:

std::function<void()> baz = foo;

How does std::function (apparently) manage to circumvent this restriction in the type system?

回答1:

What is the rationale behind that error? Why doesn't the type system allow me to ignore the answer?

The reason is that the types are different, and the generated code at the place of call (through the function pointer) is different. Consider a calling convention where all arguments are written to the stack and space for the return value is also reserved in the stack. If the call goes through a void (*)() then no space will be reserved in the stack for the return value, but the function (unaware of how it is being called) will still write the 42 to the location where the caller should have reserved space.

How does std::function (apparently) manage to circumvent this restriction in the type system?

It does not. It creates a function object that wraps the call to the actual function. It will contain a member like:

void operator()() const {
   foo();
}

Now when the compiler processes the call to foo it knows what it has to do to call a function that returns an int and it will do so according to the calling convention. Because the template does not return, it will just ignore the value --that was actually returned.



回答2:

std::function need only be source compatible- that is, it can generate a new class which generates new caling code that ignores the result. The function pointer must be binary compatible and cannot do that job- void(*)() and int(*)() point to the exact same code.



回答3:

You can think of std::function<> doing this for your particular case:

void __func_void()
{
    foo();
}

It's actually a bit more complicated than that, but the point is that it generates template code together with type-erasure to not care about the specifics.



回答4:

In addition to what others have been saying, the caller also need the return type to know what destructor it should invoke on the result (the return value may be a temporary).


Unfortunately it is not as easy as

auto (*bar)() = foo; 

Although GCC and Clang accept this. I need to recheck the spec to see whether that's actually correct.

Update: The spec says

The auto type-specifier signifies that the type of a variable being declared shall be deduced from its initializer or that a function declarator shall include a trailing-return-type.

This can be misleading when read fast, but this is implemented by GCC and clang to only apply to the toplevel declarator. In our case, this is a pointer declarator. The declarator nested in it is a function declarator. So just substitute auto for void and then the compiler will deduce the type for you.


By the way, you can always make this work manually, but it takes some trickery to make it work

template<typename FunctionType>
struct Params; 

template<typename ...Params>
struct Params<void(Params...)> {
  template<typename T>
  using Identity = T;

  template<typename R>
  static Identity<R(Params...)> *get(R f(Params...)) {
    return f;
  }
};

// now it's easy
auto bar = Params<void()>::get(foo);