Home > Software design >  Why do reference types behave differently with function pointers and std::function?
Why do reference types behave differently with function pointers and std::function?

Time:01-24

(Google translate)

Function pointers and std::function objects behave differently in the following case:

  • The actual function definition takes a value as a parameter.
  • The definition of a function pointer (or std::function object) specifies that it takes a reference as a parameter.

Code:

#include <cstdint>
#include <format>
#include <functional>
#include <iostream>

void Foo(std::uintptr_t value, std::uintptr_t& reference)
{
    std::cout << std::format("value={}(0x{:x}), reference={}", value, value, reference) << std::endl;
    std::cout << std::format("&value={}, &reference={}", (void*)&value, (void*)&reference) << std::endl;
    std::cout << std::endl;
}

int main(int argc, char* argv[])
{
    std::uintptr_t value = 1234;

    std::cout << std::format("&value={}", (void*)&value) << std::endl;
    std::cout << std::endl;

    std::cout << "[function pointer]" << std::endl;
    auto function_pointer = (void(*)(std::uintptr_t&, std::uintptr_t&))(&Foo);
    function_pointer(value, value);

    std::cout << "[function object]" << std::endl;
    std::function<void(std::uintptr_t&, std::uintptr_t&)> function_object = Foo;
    function_object(value, value);

    return 0;
}

Output:

&value=0xc26ab6f770

[function pointer]
value=835014031216(0xc26ab6f770), reference=1234
&value=0xc26ab6f6f0, &reference=0xc26ab6f770

[function object]
value=1234(0x4d2), reference=1234
&value=0xc26ab6f6f0, &reference=0xc26ab6f770

Expected Output:

&value=0xc26ab6f770

[function pointer]
value=835014031216(0xc26ab6f770), reference=1234
&value=0xc26ab6f6f0, &reference=0xc26ab6f770

[function object]
value=835014031216(0xc26ab6f770), reference=1234
&value=0xc26ab6f6f0, &reference=0xc26ab6f770

Why does this difference occur? Is the result of std::function a defined behavior?

Edit

Sorry. It looks like I didn't write the question properly.

My specific situation is:
I'm using std::function in my code, and I've set the std::function type to std::function<ReturnType(ArgTypes&...)> using a template. Since std::function's parameters are specified as reference types, I thought that the program wouldn't work properly if I stored a function that took a non-reference parameter in a std::function object. However, when I tried storing a function that takes a non-reference parameter into a std::function object, I didn't get any compiler warnings (MSVC ), and the program seemed to work fine.

So, the point is:
Why does my code compile without any compiler warnings and run fine despite the fact that the signatures of the actual function and the std::function object do not match? (https://godbolt.org/z/bzneYY6Pe)

As far as I know, reference types work internally by passing the address of the value instead of the value itself at the position of the argument.

So I thought that if I specified the type of a std::function parameter to be a reference type even though it wasn't really a reference type, my program wouldn't work because the address of the value, not the value itself, would be passed to the function.

CodePudding user response:

(void(*)(std::uintptr_t&, std::uintptr_t&)) is not allowed. You cannot cast one type of function pointer to a different type of function pointer, then call it.

Actually, the cast is okay, if you don't call the casted function pointer.

Function pointers come from the C language. The C language is quite simple and function pointers are quite dumb. The function pointer only remembers the address of the function code. The compiler does exactly what you tell it: it calls a function whose type is void(std::uintptr_t&, std::uintptr_t&). Even if that is not the correct type, the compiler thinks it is that type when it prepares for the call. It puts the address of value into the argument 1 register. Then it starts running the function code. The function code displays the number in the argument 1 register.

You cannot rely on it always working this way. It works this way on your CPU with your compiler. However, it may be different if you change the CPU, or change the compiler, or enable optimizations, for example. It could even crash the program, so don't do it. This is called "undefined behaviour".

std::function is smarter. std::function can remember more things, like how to convert arguments. When you create the std::function, it remembers the address of the function code, and it also remembers that argument 1 is a reference and shouldn't be. When you call the std::function, the compiler puts the address of value in the argument 1 register, then converts the address to the value, then the computer starts running the function code.

  •  Tags:  
  • c
  • Related