为什么 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) == 4sizeof(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 - 为什么我没有收到有关缩小转换的警告?当函数按值而不是按引用传递时,我会这样做...

确实很标准

test1test2 被转换为 匿名临时 charunsigned 类型,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,因为特化不参与重载决策的第一步。