为啥这个函数指针赋值在直接赋值而不是使用条件运算符时有效?

Posted

技术标签:

【中文标题】为啥这个函数指针赋值在直接赋值而不是使用条件运算符时有效?【英文标题】:Why does this function pointer assignment work when assigned directly but not with the conditional operator?为什么这个函数指针赋值在直接赋值而不是使用条件运算符时有效? 【发布时间】:2019-11-14 23:23:17 【问题描述】:

(此示例未使用 #include,在 MacOS10.14、Eclipse IDE 上编译,使用 g++,选项 -O0 -g3 -Wall -c -fmessage-length=0)

假设这个变量声明:

int (*fun)(int);

编译失败,“std::toupper 和 std::tolower 的重载无效”。

fun = (1 ? std::toupper : std::tolower);   // ERROR, invalid overload

编译成功:

if (1) 
    fun = std::toupper;   // OK

else 
    fun = std::tolower;   // OK

【问题讨论】:

评论不用于扩展讨论;这个对话是moved to chat。 【参考方案1】:

std::toupper(1 和 2) 和 std::tolower(1 和 2) 过载。在为conditional operator(在分配给chr2fun之前)确定它们之间的公共类型时,无法确定应该使用哪种重载。

您可以使用static_cast 指定应考虑哪一个。 (没错,强制overload resolution是先分别发生的,那么确定普通类型的麻烦就消失了。)

static_cast 也可用于通过将函数到指针转换为特定类型来消除函数重载的歧义

例如

chr2fun = (str2modus == STR2UP ? static_cast<int(*)(int)>(std::toupper) 
                               : static_cast<int(*)(int)>(std::tolower));

对于第 2 种情况,chr2fun 直接赋值; chr2fun 的类型是显式的,将在 overload resolution 中选择正确的重载。

(强调我的)

在所有这些上下文中,从重载集中选择的函数是其类型与目标期望的函数指针、函数引用或成员函数类型的指针匹配的函数:正在初始化的对象或引用,赋值的左侧、函数或运算符参数、函数的返回类型、强制转换的目标类型或模板参数的类型。

【讨论】:

添加到答案中,在没有 std:: 的情况下使用它们。 @MichaelChourdakis 如果使用了#include &lt;cctype&gt;,则未指定是否有效(实现必须将名称放在std,但也可以选择将它们放在全局命名空间中) @MartinBonner 我试过了,但两者都是必需的。 wandbox.org/permlink/NLB23HjYBiReU7Tx @BartekBanachewicz 这并不容易回答......我认为这只是标准不需要编译器这样做;它可能需要更复杂的分析。 @BartekBanachewicz 通常“可选地放置在全局命名空间中”的原因是因为不同的实际实现做不同的事情,并且要求强制或禁止这样做会给某些实现带来过度的实现工作。 (我有一点设定,C++98 试图坚持“std::only”带有一些头文件,几乎所有的实现都继续并忽略了它们,因为它很难与现有的 C 库集成。) 【参考方案2】:

在第一种情况下,编译器在进行赋值之前就犹豫了。一个简化的表达式:

(true ? std::toupper : std::tolower)

如果存在多个toupper/tolower 重载,将无法编译。这是因为三元运算符的返回类型必须仅根据第二个和第三个参数的类型来确定,而无需查看使用它的结果的上下文。

很有趣,即使其中一个参数不是重载函数,这仍然不够。其原因不太明显,更多地与overload resolution1 规则及其适用位置有关。强制转换恰好是触发它的七种可能性之一,而确定三元运算符的目标类型本身不是。

在直接赋值的情况下,赋值的右轴必须适合左轴,所以没有歧义。

无论如何,正如@Caleth 所指出的,根据16.5.4.2.1.6,此代码具有未指定的行为。


1C++ 参考有一个不正确的 C++ 标准段落。 [over.over] 实际上是 12.4。

【讨论】:

thx,这是真正有用的信息,但在这种情况下,toupper 和 tolower 具有相同的签名...? @phh 由于重载,它们具有相同的 sets 签名。应该使用哪个匹配对来确定?: 的最终类型? 天哪,我是初学者! thx 很多,这回答了我的问题! :-) Caleth 的参考来自草稿 C++2a。在那之前呢?【参考方案3】:

这个snippet 用 gcc 9.1 编译得很好

#include <cctype>

int chr2fun(bool str2modus) 
    const bool STR2UP = true;
    int (*chr2fun)(int);

    if (str2modus == STR2UP) 
        chr2fun = std::toupper;
     else 
        chr2fun = std::tolower;
    
    chr2fun = (str2modus == STR2UP ? std::toupper : std::tolower);

您在哪个平台和哪个编译器上收到错误?

【讨论】:

我怀疑他们会包含&lt;locale&gt;,这会为std::toupper等引入额外的重载。

以上是关于为啥这个函数指针赋值在直接赋值而不是使用条件运算符时有效?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在直接初始化和赋值中传递 lambda 而不是复制初始化时会编译?

为啥要使用三元运算符而不为“真”条件赋值 (x = x ?: 1)

拷贝构造函数和赋值运算符的认识

为啥使用闭包进行赋值而不是直接为键赋值?

为啥定义了移动构造函数而隐式删除了赋值运算符?

c语言char和int可以互相赋值,但指针不能赋值,char *和int *不能直接赋值,为啥呢?谢谢