为什么 double 可转换为看似任何原始类型的 const 引用?
Why is a double convertible to a const-reference of seemingly any primitive?
考虑以下代码:
#include <iostream>
float func(char const & val1, unsigned int const & val2)
{
return val1 + val2;
}
int main() {
double test1 = 0.2;
double test2 = 0.3;
std::cout << func(test1, test2) << std::endl;
return 0;
}
尽管我将 double
传递给一个函数,该函数采用对小于 double
的类型的常量引用(在我的系统上,sizeof(double) == 8
,而 sizeof(unsigned int) == 4
和 sizeof(char) == 1
根据定义)。如果引用不是 const
,则编译失败(例如,float func(char & val1, unsigned int & val2)
而不是当前定义)并出现错误:
cannot bind non-const lvalue reference of type 'char&' to an rvalue of type 'char'
在 Godbolt 上使用 GCC、Clang、ICC 和 MSVC 测试时,我得到了完全相同的行为,所以它看起来很标准。 const-references 是什么导致 this 被接受,而 reference 不被接受?另外,我使用了 -Wall -pedantic
- 为什么我没有收到有关缩小转换的警告?当函数按值而不是按引用传递时,我会这样做...
确实很标准
test1
和 test2
被转换为 匿名临时 char
和 unsigned
类型,const
函数中的引用是适当的绑定。如果您将编译器设置为警告您缩小转换(例如 -Wconversion),它会输出一条消息。
如果函数参数是非 const
引用,则这些绑定是不可能的,并且您的编译器在这种情况下会正确发出诊断。
一个解决方法是 delete
更好的重载匹配:
float func(double, double) = delete;
作为已接受答案的补充,尤其是方法
One fix is to delete a better overload match:
float func(double, double) = delete;
也可以从其他方面来处理它:即删除所有与您预期的参数类型不完全匹配的重载。如果你想避免 any 隐式转换(包括提升),你可以将 func
定义为删除的 non-overloaded 函数模板,并定义 [=12 的显式特化=] 仅适用于您希望为其重载的特定类型的参数。例如:
// Do not overload the primary function template 'func'.
// http://www.gotw.ca/publications/mill17.htm
template< typename T, typename U >
float func(const T& val1, const U& val2) = delete;
template<>
float func(char const& val1, unsigned int const& val2)
{
return val1 + val2;
}
int main() {
double test1 = 0.2;
double test2 = 0.3;
char test3 = 'a';
unsigned int test4 = 4U;
signed int test5 = 5;
//(void)func(test1, test2); // error: call to deleted function 'func' (... [with T = double, U = double])
//(void)func(test2, test3); // error: call to deleted function 'func' (... [with T = double, U = char])
(void)func(test3, test4); // OK
//(void)func(test3, test5); // error: call to deleted function 'func' (... [with T = char, U = int])
return 0;
}
再次强调要重载主函数模板时要小心,因为重载和显式特化函数模板的重载决策 can be somewhat confusing,因为特化不参与重载决策的第一步。
考虑以下代码:
#include <iostream>
float func(char const & val1, unsigned int const & val2)
{
return val1 + val2;
}
int main() {
double test1 = 0.2;
double test2 = 0.3;
std::cout << func(test1, test2) << std::endl;
return 0;
}
尽管我将 double
传递给一个函数,该函数采用对小于 double
的类型的常量引用(在我的系统上,sizeof(double) == 8
,而 sizeof(unsigned int) == 4
和 sizeof(char) == 1
根据定义)。如果引用不是 const
,则编译失败(例如,float func(char & val1, unsigned int & val2)
而不是当前定义)并出现错误:
cannot bind non-const lvalue reference of type 'char&' to an rvalue of type 'char'
在 Godbolt 上使用 GCC、Clang、ICC 和 MSVC 测试时,我得到了完全相同的行为,所以它看起来很标准。 const-references 是什么导致 this 被接受,而 reference 不被接受?另外,我使用了 -Wall -pedantic
- 为什么我没有收到有关缩小转换的警告?当函数按值而不是按引用传递时,我会这样做...
确实很标准
test1
和 test2
被转换为 匿名临时 char
和 unsigned
类型,const
函数中的引用是适当的绑定。如果您将编译器设置为警告您缩小转换(例如 -Wconversion),它会输出一条消息。
如果函数参数是非 const
引用,则这些绑定是不可能的,并且您的编译器在这种情况下会正确发出诊断。
一个解决方法是 delete
更好的重载匹配:
float func(double, double) = delete;
作为已接受答案的补充,尤其是方法
One fix is to delete a better overload match:
float func(double, double) = delete;
也可以从其他方面来处理它:即删除所有与您预期的参数类型不完全匹配的重载。如果你想避免 any 隐式转换(包括提升),你可以将 func
定义为删除的 non-overloaded 函数模板,并定义 [=12 的显式特化=] 仅适用于您希望为其重载的特定类型的参数。例如:
// Do not overload the primary function template 'func'.
// http://www.gotw.ca/publications/mill17.htm
template< typename T, typename U >
float func(const T& val1, const U& val2) = delete;
template<>
float func(char const& val1, unsigned int const& val2)
{
return val1 + val2;
}
int main() {
double test1 = 0.2;
double test2 = 0.3;
char test3 = 'a';
unsigned int test4 = 4U;
signed int test5 = 5;
//(void)func(test1, test2); // error: call to deleted function 'func' (... [with T = double, U = double])
//(void)func(test2, test3); // error: call to deleted function 'func' (... [with T = double, U = char])
(void)func(test3, test4); // OK
//(void)func(test3, test5); // error: call to deleted function 'func' (... [with T = char, U = int])
return 0;
}
再次强调要重载主函数模板时要小心,因为重载和显式特化函数模板的重载决策 can be somewhat confusing,因为特化不参与重载决策的第一步。