3

I have a program that has to store functions as void (*) (void*). Creating functions with that signature leads to code duplication (usually the first line is to cast the void pointer to the correct type) and reduces type safety (the void pointer can be cast to anything, such as the wrong type), so I was wondering if I can take a function of type void (*)(T*), and then convert it to a void(*)(void*) and call it like that, in a way similar to this:

#include <iostream>
#include <string>

void printer(std::string* string)
{
    std::cout << *string << std::endl;
}

int main() 
{
    //Cast the function to a one that takes a void pointer
    auto func = reinterpret_cast<void(*)(void*)>(&printer);
    //Create a string and call the function with it
    std::string string = "Hello World";
    func(&string);
    return 0;
}

The above code compiles and runs fine (on ideone) but I was wondering if it's at all standards compliant or if it's undefined behaviour and is just working correctly for my specific example and OS

6
  • 1
    I'm thinking this falls into "Yeah, probably going to work." But it's not kosher. Pretty sure it's violating the strict aliasing rule. Commented Jan 22, 2019 at 23:02
  • 1
    I'd go with something more along the lines of ideone.com/P8fV3K but I'm certain someone will come along in a moment with a nifty bit of lambda magic that clears up the whole problem. Commented Jan 22, 2019 at 23:07
  • 1
    @user4581301: I don't see how lambda can be used here. It would need to capture typed function ptr ([&print](void* value) {...}), but capture makes it "stateful" and now it can't be converted to function ptr. Commented Jan 22, 2019 at 23:44
  • 1
    @AndriyTylychko Since printer here is a namespace member and not a function-local variable, lambda capture doesn't apply to it. Commented Jan 22, 2019 at 23:49
  • 1
    I will admit to being smurf-poor in my lambda skills. Ergo I didn't even try to answer the question. Commented Jan 22, 2019 at 23:54

2 Answers 2

5

This is undefined behavior.

[expr.call]/1:

Calling a function through an expression whose function type is different from the function type of the called function's definition results in undefined behavior.

[expr.reinterpret.cast]/6:

A function pointer can be explicitly converted to a function pointer of a different type. Except that converting a prvalue of type "pointer to T1" to the type "pointer to T2" (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.

C++ doesn't allow for very many function casts at all to work, even for things you might think would be safe like changing const details. When you need such a thing, use an explicit wrapper function instead. A non-capturing lambda could be one easy way to write one without naming it; or you could define a general template to wrap other functions in a way you need.

The one case which is allowed, since C++17, is converting a pointer to non-throwing function (e.g. marked noexcept) to a potentially-throwing function pointer type (without exception specification or noexcept(false)), then calling it via the potentially-throwing function type. This sort of function pointer conversion is allowed implicitly, so no reinterpret_cast or static_cast or other is even needed.

Sign up to request clarification or add additional context in comments.

Comments

3

Behaviour of doing so is undefined. Standard (draft) says:

[expr.reinterpret.cast] A function pointer can be explicitly converted to a function pointer of a different type. [Note: The effect of calling a function through a pointer to a function type (9.2.3.5) that is not the same as the type used in the definition of the function is undefined. — end note] Except that converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified. [Note: See also 7.3.11 for more details of pointer conversions. — end note]


You could use a lambda:

void(*func)(void*) = [](void *string) {
    printer(static_cast<std::string*>(string));
};

4 Comments

I suppose OP is trying to get rid of this reinterpret_cast. now it's not inside print function but in the place where function ptr is stored. isn't this even worse as the type info is somewhere else and to refactor the function type we must remember to update this place too?
@AndriyTylychko It's still ugly, but at least now it's Standard-compliant: a reinterpret_cast from void* to the exact original pointer type is guaranteed to reverse a standard conversion to void* (as of C++11; in C++03 not even that was guaranteed). Though actually, in this case a static_cast would also be correct.
@AndriyTylychko this will fail to compile if the argument isn't something that pointer to string would implicitly convert to, so the compiler will help you remember to update. You can use something like boost::function_traits to deduce the type of the argument to be more convenient.
@aschepler good point. static_cast is sufficient and more appropriate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.