【问题标题】:Why does the first function call bind to the first function? [duplicate]为什么第一个函数调用绑定到第一个函数? [复制]
【发布时间】:2014-12-30 17:24:05
【问题描述】:

为什么第一个函数调用 (cm(car);) 绑定到第一个函数?

我知道第二个调用绑定到第二个函数,因为它不是模板,尽管两者都是完美匹配。

如果第一个函数被定义为固定数组长度的非模板,为:

    void cm(const char (&h)[8]) {cout << "const char (&)[8]" << endl;}

它再次被选中而不是第二个(第二个调用将是模棱两可的)。

代码:

template<size_t N> void cm(const char (&h)[N]) 
    {std::cout << " const (&)[N] " << endl;}

void cm(const char * h)
    {cout << " const char * " << endl;}

int main()
{
    char car[] = "errqweq";
    const char ccar[] = "errqweq";
    cm(car);
    cm(ccar);
}

输出:

 const (&)[N]
 const char * 

【问题讨论】:

    标签: c++ function templates language-lawyer overload-resolution


    【解决方案1】:

    第一次调用选择函数模板特化 - 因为它是更好的匹配。
    让我们标记两个重载:

    template<size_t N> void cm(const char (&h)[N])  // (1) - the specialization
        {std::cout << " const (&)[N] " << endl;}
    
    void cm(const char * h)                         // (2)
        {cout << " const char * " << endl;}
    

    对于 (1),car 绑定到一个引用。这是一个身份转换1。 对于 (2),在 car 的数组到指针转换后,产生 char*2,必须进行限定转换,因此 char* 变为
    char const* .现在调用这个:

    标准转换序列S1 比转换序列更好 标准转换序列S2if

    • S1S2 的正确子序列(比较 13.3.3.1.1 定义的规范形式的转换序列,不包括任何 左值变换;身份转换序列是 被认为是任何非身份转换的子序列 序列)或者,如果不是这样,
    • […]

    数组到指针的转换是左值转换,所以这里不考虑它——就像在第二个例子中一样。不过,资格转换有一个自己的类别:资格调整。因此,转换为 (1) 的参数是转换为 (2) 的参数的子序列:第一个是恒等转换,第二个是资格转换,根据上面的段落,恒等转换是任何非身份转换。所以选择了(1)。

    正如您自己已经提到的,在第二种情况下,转化率同样好;上面的引用不起作用,因为转换为 (2)s 参数不是转换为 (1) 的参数的子序列。因此,[over.match.best]/1 适用。

    鉴于这些定义,一个可行的函数F1 被定义为 如果所有参数都比另一个可行的函数F2 更好的函数 i,ICSi(F1)不是比ICSi(F2)差的转换序列,那么

    • 对于某些参数 j,ICSj(F1) 是比 ICSj(F2) 更好的转换序列,或者,如果不是这样,
    • 上下文是由用户定义的转换 […] 进行的初始化,或者,如果不是这样,
    • F1 是非模板函数,F2 是函数模板特化

    所以(2)选择了一个。如果函数模板不是模板而是带有参数
    char const (&amp;)[8] 的函数,则调用将是模棱两可的 as Clang correctly says


    1 [over.ics.ref]/1:

    当引用类型的参数直接绑定 (8.5.3) 到 参数表达式,隐式转换序列是恒等式 转换,除非参数表达式的类型是 参数类型的派生类,在这种情况下隐式 转换序列是派生到基础的转换 (13.3.3.1)。

    [dcl.init.ref]/5(在 8.5.3 中):

    在所有情况下,除了最后一个(即,创建和初始化一个 来自初始化表达式的临时),该引用被称为 直接绑定到初始化表达式。


    2 [conv.array]:

    N T 数组”或“未知数组”类型的左值或右值 T 的边界可以转换为“指向T 的指针”类型的纯右值。 结果是指向数组第一个元素的指针。

    T 可以是 cv 限定的,指针的类型也是如此。这里T 就是char,所以指针的类型是指向char => char*

    【讨论】:

    • “直接绑定”似乎没有由 8.5.3 定义。您是说将非常量数组绑定到 const 引用算作“直接绑定”?
    • @MattMcNabb 因为看起来这可能会让更多人感到困惑,所以我添加了一个脚注。 :)(已定义,见我的脚注)
    • 啊,我忽略了这一点。所以它意味着任何不涉及临时的引用绑定。
    【解决方案2】:

    因为直接写在代码中的字符串“errqweq”是只读的,因为它在运行时处于“受保护”的一部分内存中,因为它作为常量进行管理。

    使用const char* ccar;const char ccar[]; 指向它是正确的。您使用 const 说明符指向保存原始“errqweq”的内存:编译器确保不会修改字符串。

    但是看看:char car[] = "errqweq";

    为了为您提供一个可修改的缓冲区(正如您在没有 const 修饰符的情况下请求的那样),编译器在堆栈上创建一个包含 8 个元素(7 个字符 + \0)的数组,并在其中复制(即:初始化它)字符串“errqweq”。

    所以第一次调用是使用char buffer[8] 参数,该参数安全地转换为const char buffer[8]。显然,数组的固定大小可以与模板进行最佳匹配,而不是与需要“仅”常量指针的函数进行更“弱”的绑定。

    【讨论】:

    • 嗯,我从来不知道const char[] 会使用字符串文字来做到这一点,我认为在这种情况下它会表现得像char[]
    • 你答案最后一段的论证是不正确的,不是吗?
    猜你喜欢
    • 2022-08-11
    • 1970-01-01
    • 2019-10-19
    • 2021-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-08
    • 1970-01-01
    相关资源
    最近更新 更多