整数参数调用浮点重载,浮点参数调用整数重载?

12
今天我遇到了大致如下的代码:
#include <iostream>

void f(float&& f) { std::cout << f << "f "; }
void f(int&& i) { std::cout << i << "i "; }

int main()
{
    int iv = 2; float fv = 1.0f;
    f(2);  f(1.0f);
    f(iv); f(fv);
}

Godbolt link

前两个f-calls打印出了2i 1f,正如预期的那样。

现在对于第二行,我本来期望它要么根本无法编译,因为iv和fv不是临时变量(因此无法绑定到右值引用),要么它会创建变量的副本传递给函数,因此会再次打印2i 1f

然而,不知何故它打印出了2f 1i,这是我最不期望的结果。

如果你将代码复制到cppinsights中,它会将调用转换为

f(static_cast<float>(iv));
f(static_cast<int>(fv));

所以它似乎非常有意地将整数转换为浮点数,然后将浮点数转换为整数,但我不知道为什么会这样,也不知道如何在谷歌上搜索相关信息。为什么会发生这种情况?是什么规则导致了这个结果?

1
f(iv); 无法绑定到 f(int&&),但可以通过临时对象绑定到 f(float&&)。同样地,f(fv) 也是如此。 - undefined
4
@Eljay 有时候我真的很讨厌隐式转换 - undefined
@PepijnKramer • Sean Baxter的New Circle实验允许您选择退出隐式转换。 - undefined
1
是的,或者cpp2(Herb Sutter)。就像Bjarne所说的,C++中隐藏着一种更好的语言,我们只需要将其发掘出来。但还是谢谢你的参考;) - undefined
一个 int 不会被“转换”为一个 int,它就是一个 int。因此没有进行转换。正是转换才会产生一个临时变量。你可以通过 f(iv+0) 强制生成临时变量(我认为 f(+iv) 也可能起作用)。 - undefined
显示剩余2条评论
3个回答

8
程序的行为可以从reference-initialization中理解。
dcl.init#ref-5.4中:

[Example 6:

double d2 = 1.0;
double&& rrd2 = d2;                 // error: initializer is lvalue of related type
int i3 = 2;
double&& rrd3 = i3;                 // rrd3 refers to temporary with value 2.0

-end example]


案例1

在这里我们讨论为什么void f(int&& i)对于调用f(iv)来说不可行。

左值iv不能绑定到void f(int&& i)中的右值引用参数i,因此重载f(int&&)不可行。基本上,int&& i = iv;是不允许的,因为iv是一个相关类型的左值


案例2

在这里我们讨论为什么void f(float&& i)对于调用f(iv)是可行的。

对于调用f(iv),重载void f(float&& f)是可行的,因为首先初始化表达式iv会被隐式转换为目标类型(float)的prvalue,然后可以进行临时材料化,使得参数f可以绑定到被材料化的临时变量2.0f(这是一个xvalue)。


类似于调用 `f(fv)`,重载 `void f(float&& i)` 不可行,因为 `fv` 是相关类型的左值。而对于调用 `f(fv)`,重载 `void f(int&& i)` 可以使用,因为首先初始化器被隐式转换为一个纯右值,然后进行临时材料化,使得 `i` 可以绑定到材料化的临时变量 `1`(类型为 `int`)。

所以如果是相同类型的变量,它们会使函数无法使用以保护你(因为显然你不想将一个变量放入一个为临时变量设计的函数中),但是当类型不同时,突然之间就可以了,因为你必须创建一个临时变量(强制类型转换的结果)来调用函数。一方面我有点能理解这种逻辑,但同时,它真的让我希望我学的是Java而不是这个。 - undefined
@user22879756 基本上,你的程序与int iv = 2; int&& i = iv; /*不起作用*/ float&& f = iv; //这样可以工作是一样的。所以这与特定的函数无关。 - undefined

4
为了避免隐式转换被考虑,请按照以下方式重写您的代码。使用C++17语法(因为您在godbolt链接中使用了它) 至少现在有错误的代码将无法编译。
通过明确约束,类型必须完全匹配,不考虑隐式转换。(C++20语法会更好一些)
演示:https://godbolt.org/z/MexsGT55T
#include <iostream>
#include <type_traits>

template<typename type_t>
auto f(type_t&& f) -> std::enable_if_t<std::is_same_v<type_t,float>,void>
{ 
    std::cout << f << "f "; 

}

template<typename type_t>
auto f(type_t&& f) -> std::enable_if_t<std::is_same_v<type_t,int>,void>
{ 
    std::cout << f << "f "; 

}

int main()
{
    int iv = 2;
    float fv = 1.0f;

    f(2);  f(1.0f);
    f(iv); f(fv); // <== will no longer compile now
}

稍微好看一点点(但还是有点丑;P):https://godbolt.org/z/Y33WP8ae5 - undefined
1
或者C++20:https://godbolt.org/z/z66EM7fM8 - undefined
在C++20中,你可以使用same_as代替is_same,例如auto f(std::same_as<int> auto && f) :) - undefined
@alagner 谢谢,我还在努力适应C++20/concepts。对于它们的熟悉程度远不及SFINAE ;) - undefined

2
为了避免隐式转换,提供一个删除函数模板,只有在非模板化的重载不完全匹配时才会被选择。也就是说,只需添加这个重载即可:
template<typename T>
void f(T&&) = delete;

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