重载解析和模板参数推导 - 为什么 0 很特殊?

tox*_*xic 28 c++ templates function-templates overload-resolution template-argument-deduction

在下面的示例中,0它以一种特殊的方式运行:它选择与示例函数调用所期望的不同的重载。我想知道为什么。我的理解也如下。

#include <iostream>

template<typename T>
void f(T a) {
    std::cout << "first" << std::endl;
}

template<typename T>
void f(T* a) {
    std::cout << "second" << std::endl;
}

int main()
{
    f(0);
    f<size_t>(0);
    f<size_t>(0UL);
    
    f(1);
    f<size_t>(1);
}
Run Code Online (Sandbox Code Playgroud)

输出:

first
second
first
first
first
Run Code Online (Sandbox Code Playgroud)

我的理解:

f(0)- 模板参数推导,整数文字0int类型,因此f选择第一个T=int

f<size_t>(0)-带有整数提升的显式模板实例化,选择的类型是T=size_t,选择第一个函数并从到提升0(我在这里错了intsize_t

f<size_t>(0UL)- 与上面相同,但没有升级(0 已经是 type size_t

f(1)- 与 1 相同。

f<size_t>(1)- 与2相同。(我因为某种原因在这里??)

笔记:

我知道它0可以隐式转换为空指针:

空指针常量是一个值为 0 的整数文字 (5.13.2) 或 std::nullptr_t 类型的纯右值

不过,我从标准中也知道,推广的优先级高于转化:

每种类型的标准转换序列都被分配以下三个等级之一:

  1. 精确匹配:无需转换、左值到右值转换、限定转换、函数指针转换、(C++17 起)用户定义的类类型到同一类的转换
  2. 促销方式:积分促销、浮点促销
  3. 转换:整数转换、浮点转换、浮点积分转换、指针转换、指针到成员转换、布尔转换、派生类到其基类的用户定义转换

use*_*570 27

f<size_t>(0)- 具有整数提升的显式模板实例化,选择类型为T=size_t,选择第一个函数,并将 0 从 int 提升为size_t

f<size_t>(0)调用/使用第二个重载的原因f(T*)是因为部分排序规则。特别地,第二个重载比第一个重载f(T* a)专门f(T a)

请注意,如果我们使用普通(非模板)函数,void f(std::size_t*)那么void f(std::size_t)调用f(0)将是不明确的,但使用函数模板(如您的情况),调用更f(0)喜欢/选择void f(T*)版本,因为它比void f(T)


为什么f(0)调用第一个重载。我知道 0 可以隐式转换为空指针:

因为模板参数推导时不考虑隐式转换。这意味着对于调用来说,f(0)只有第一个重载void f(T a)可行的。换句话说,第二次重载void f(T* a)对于调用来说甚至不可行f(0)。您可以在此演示中验证这一点:

template<typename T>
void f(T* a) {
    std::cout << "second" << std::endl;
}
int main()
{
    f(0);//FAILS because implicit conversion are not considered during deduction   
}
Run Code Online (Sandbox Code Playgroud)

  • @有毒,好的,我也添加这一点。但简而言之,“f(0)”调用第一个重载,因为**在推导过程中不考虑隐式转换!!**这意味着对于调用“f(0)”,只有第一个重载是可行的!您可以在[此处](https://godbolt.org/z/Gd1oPzE8Y)验证这一点 (2认同)
  • @有毒我已经添加了为什么“f(0)”在我的答案中调用第一个重载的解释。查看更新的答案和演示/示例。 (2认同)
  • 我猜这是因为 MSVC 中“0UL”没有“size_t”类型,必须使用“0ULL”来代替。或者更好的“f&lt;size_t&gt;(size_t(0))”,它适用于所有编译器。 (2认同)