今天我遇到了大致以下代码:
#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);
}
前两个 f 调用打印
2i 1f
,如预期。
现在对于第二行,我预计它要么根本不编译,因为 iv 和 fv 不是临时变量(因此不能绑定到 r 值引用),或者它创建变量的副本传递给函数,然后再次打印
2i 1f
。
但是,不知怎的,它打印了
2f 1i
,这几乎是我最不想看到的事情。
如果将代码复制到 cppinsights 中,它会将调用转换为
f(static_cast<float>(iv));
f(static_cast<int>(fv));
所以它看起来非常有意地将整数转换为浮点数,然后将浮点数转换为整数,但我不知道为什么这样做,而且也不知道如何谷歌。为什么会出现这种情况?导致这个结果的规则是什么?
为了避免考虑隐式转换,请像这样重写代码。使用 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
}
为了避免隐式转换,请提供一个删除函数模板,如果非模板化重载不完全适合,则选择该模板。也就是说,只需添加这个重载:
template<typename T>
void f(T&&) = delete;
程序的行为可以从引用初始化来理解。
来自 dcl.init#ref-5.4:
【例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
-结束示例]
这里我们讨论为什么调用
void f(int&& i)
对于调用f(iv)
不可行。
左值
iv
无法绑定到i
中的右值引用参数void f(int&& i)
,因此对于调用f(iv)
,重载f(int&&)
不是可行。基本上,int&& i = iv;
是不允许的,因为iv
是相关类型的左值。
但是对于调用
f(iv)
,第一个重载(void f(float&& f)
)是可行的(并且匹配),因为/通过临时物化。该标准甚至有具体的示例,如您的情况。