将函数指针类型转换

3
我有一个程序必须将函数存储为void (*) (void*)。创建具有该签名的函数会导致代码重复(通常第一行是将void指针转换为正确的类型),并减少类型安全性(void指针可以被转换为任何内容,例如错误的类型)。因此,我想知道是否可以将类型为void (*)(T*)的函数转换为void(*)(void*),并以类似以下方式调用它:
#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;
}

上述代码在ideone上编译并运行正常,但我想知道它是否符合标准,或者它是否是未定义的行为,并且只对我的特定示例和操作系统正常工作。

@user4581301 哦,没错!我在这里和 ideone 上编辑了代码,它仍然有效。 - Ben Hollier
1
我认为这属于“是的,可能会工作”。但这不是合法的。相当确定它违反了严格别名规则。 - user4581301
1
我会选择类似https://ideone.com/P8fV3K的东西,但我相信很快就会有人使用巧妙的lambda魔法来解决整个问题。 - user4581301
1
@user4581301:我不明白 lambda 表达式在这里怎么用。它需要捕获函数指针类型([&print](void* value) {...}),但是捕获会使其“有状态”,现在它无法转换为函数指针。 - Andriy Tylychko
1
@AndriyTylychko 因为 printer 在这里是命名空间成员而不是函数局部变量,所以 lambda 捕获并不适用于它。 - aschepler
1
我承认我的lambda技能很差。因此,我甚至没有尝试回答这个问题。 - user4581301
2个回答

4
这是未定义行为。

[expr.call]/1:

通过一个函数类型与被调用函数定义的函数类型不同的表达式调用函数会导致未定义行为。

[expr.reinterpret.cast]/6:

函数指针可以显式转换为不同类型的函数指针。除了将类型为“指向T1的指针”的prvalue转换为类型为“指向T2的指针”(其中T1和T2是函数类型)并返回其原始类型以外,此类指针转换的结果是未指定的。
C++几乎不允许进行任何函数转换,即使对于您可能认为是安全的更改“const”细节的情况也是如此。当您需要这样的功能时,请使用显式的包装函数。非捕获lambda可以是一种简单的方法来编写一个没有命名的包装函数;或者您可以定义一个通用模板,以您需要的方式包装其他函数。
自C++17以来,唯一允许的情况是将指向非抛出函数(例如标记为“noexcept”)的指针转换为潜在抛出函数指针类型(没有异常规范或“noexcept(false)”),然后通过潜在抛出函数类型调用它。这种类型的函数指针转换是隐式允许的,因此甚至不需要reinterpret_cast或static_cast等操作。

2
将其转换的行为未定义。标准(草案)表示:
``` [expr.reinterpret.cast] 函数指针可以显式地转换为不同类型的函数指针。 [注:通过指向与函数定义中使用的类型不同的函数类型(9.2.3.5)的指针调用函数的效果未定义。— end note] 除了将类型为“指向T1的指针”的prvalue转换为类型为“指向T2的指针”(其中T1和T2是函数类型),然后返回其原始类型会产生原始指针值,这种指针转换的结果是未指定的。 [注:有关指针转换的更多详细信息,请参见7.3.11。— end note] ```
您可以使用lambda表达式:
void(*func)(void*) = [](void *string) {
    printer(static_cast<std::string*>(string));
};

我想OP试图摆脱这个reinterpret_cast。现在它不在print函数内部,而是在存储函数指针的地方。难道这不更糟糕吗?类型信息在其他地方,如果重构函数类型,我们必须记得更新这个地方吗? - Andriy Tylychko
@AndriyTylychko 这仍然不够美观,但至少现在它符合标准:从void*到确切的原始指针类型的reinterpret_cast保证能够逆转标准的void*转换(自C++11起;在C++03中甚至没有这个保证)。实际上,在这种情况下,static_cast也是正确的选择。 - aschepler
@AndriyTylychko 如果参数不是指向字符串的隐式转换,那么编译将无法通过,因此编译器将帮助您记住更新。您可以使用类似于boost::function_traits的东西来推断参数类型以更方便地使用。 - eerorika
@aschepler 的观点很好。static_cast 足够且更加合适。 - eerorika

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接