模板型别推导

模板型别推导

下面代码表示:函数模板和函数调用(从 expr 来推导 T 和 ParamType 的型别)。

template<typename T>
void func(ParamType param);  // 函数模板的声明

func(expr);  // expr:表达式,从 expr 来推导 T 和 ParamType 的型别

        在编译期,编译器会通过  表达式 expr  推导两个型别(类型),一个是 T 的型别,一个是 param 的型别,而且这两个型别通常不一样。因为 ParamType 中常常会包含一些饰词,如 const 或引用符号等限定词,例如 ParamType 可以是 const T&

        本质上, T 的型别推导结果,不仅仅依赖于 expr 的型别,还依赖 ParamType 的形式,具体情况分为三种:

  1. ParamType 具有指针或引用型别,但不是万能引用;
  2. ParamType 是一个万能引用;
  3. ParamType 既非指针,也非引用;

       

情况1:ParamType 具有指针或引用型别,但不是万能引用;

在这种情况下,编译器会这样进行类型推导:

  1.  expr 具有引用型别,先将引用部分忽略。
  2. 然后,对 expr 的型别和 ParamType 的型别执行模式匹配,来决定的 T  型别.

举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename T>
void func(T& param)
{
    T t = 0;
    cout << type_name<decltype(t)>() << endl;      // 这里通过调用自定义的 type_name 函数来获取表达式(T 和 param)的型别
    cout << type_name<decltype(param)>() << endl;
}
 
int main()
{
    int x = 27;
    const int cx = x;
    const int& rx = x;   
    func(x);    // T 的型别是 int,param 的型别是 int&
    func(cx);   // T 的型别是 const int,param 的型别是 const int&
    func(rx);   // T 的型别是 const int,param 的型别是 const int&}

  当人们向引用型别的形参传入 const 对象时,他们期望对象保持其不可修改的属性,也就是说,期望该形参成为 const 的引用型别。即该对象的常量性(constness)会成为 T 的类型推导结果的组成部分。

  在第三个调用中,即使 rx 具有引用型别,T 也并未被推导成一个引用。因为 rx 的引用性(reference-ness)会在型别推导过程中被忽略。

  上述规则对于右值引用仍然成立!

如果将函数模板的形参类别从 T& 改为 const T&,结果会有一些变化,但仍然满足规则,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template<typename T>
void func(const T& param)
{
    T t = 0;
    cout << type_name<decltype(t)>() << endl;      // 这里通过调用自定义的 type_name 函数来获取表达式(T 和 param)的型别
    cout << type_name<decltype(param)>() << endl;
}
 
int main()
{
    int x = 27;
    const int cx = x;
    const int& rx = x;
 
    func(x);    // T 的型别是 int,param 的型别是 const int&
    func(cx);   // T 的型别是 int,param 的型别是 const int&
    func(rx);   // T 的型别是 int,param 的型别是 const int&
}

  一如前例,rx 的引用性在型别推导过程中会被忽略,并且函数模板的参数 param 本身具有 const 引用型别,T 的型别推导结果中包含 const 也就没有必要了。 

 

情况2:ParamType 是万能引用;

当函数模板中的参数是万能引用(即在函数模板中持有型别参数 T 时,万能引用的声明有且只能为 T&&)时,当传入的实参为左值或右值时,表现有所不同。具体如下:

  • 如果 expr 是个左值,T 和 ParamType 都会被推导为左值引用。有两点特别之处:1. 这是在模板型别推导中,被推导为引用型别的唯一情形;2. 函数模板形参是用右值引用语法声明,而它的型别推导结果却是左值引用。
  • 如果 expr 是个右值,推导结果如情况1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<typename T>
void func(T&& param)
{
    int m = 0;  
    T t = m;
    cout << type_name<decltype(t)>() << endl;      // 这里通过调用自定义的 type_name 函数来获取表达式(T 和 param)的型别
    cout << type_name<decltype(param)>() << endl;
}
 
int main()
{
    int x = 27;
    const int cx = x;
    const int& rx = x;
 
    func(x);    // T 的型别是 int&,param 的型别是 const int&
    func(cx);   // T 的型别是 const int&,param 的型别是 const int&
    func(rx);   // T 的型别是 const int&,param 的型别是 const int&
    func(27);   // T 的型别是 int,param 的型别是 int&&
}

 

情况3:ParamType 既非指针也非引用;

当 ParamType 既非指针也非引用,此时就是值传递。无论传入的是什么, ParamType 都会是它的一个副本。由 expr 推导出 T 的型别规则如下:

一如之前,若 expr 具有引用型别,则忽略;

忽略 expr 的引用型别后,若是个 const / volatile 对象,也忽略 const / volatile 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template<typename T>
void func(T param)
{
    T t = 0;
    cout << type_name<decltype(t)>() << endl;      // 这里通过调用自定义的 type_name 函数来获取表达式(T 和 param)的型别
    cout << type_name<decltype(param)>() << endl;
}
 
int main()
{
    int x = 27;
    const int cx = x;
    const int& rx = x;
 
    func(x);    // T 的型别是 int,param 的型别是 int
    func(cx);   // T 的型别是 int,param 的型别是 int
    func(rx);   // T 的型别是 int,param 的型别是 int
}

  有上述代码可知:若函数模板参数的型别为 const T param,则 T 的型别仍是 int,param 的型别是 const int。

     对于函数实参为既有顶层 const 也有底层 const的对象,那么按值传递会忽略对象的顶层 const 属性,而保留底层 const 属性。如下述代码所示:

1
2
3
4
5
6
template<typename T>
void func(T param);
 
const char* const ptr = "czw study";
 
func(ptr);   // T 的型别是 const char*,param 的型别是 const char*

 

数组实参

虽然数组有时会退化成首元素的指针,但当函数模板的参数是引用型别和非引用型别,有很大的区别。

  • 当函数模板的参数不是引用型别时,数组会退化成指针;
  • 当函数模板的参数是引用型别时,数组不会退化成指针;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename T>
void func(T param);
 
const char name[] = "czw study";
 
func(name);   // T 的型别是 const char*,param 的型别是 const char*
 
 
 
 
template<typename T>
void func(T& param);
 
const char name[] = "czw study";   // name 的型别是 const [10] const
 
func(ptr);   // T 的型别是 const char [10] const,param 的型别是 const char [10] const&

 因此,可以利用数组引用这一能力创造出一个模板,用来推导出数组含有元素的个数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 该函数模板能在编译期获取数组的个数
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept   // 模板参数为数组引用: T (&)[N]
{
    return N;
}
 
int main()
{
    int keyVals[] = { 1, 3, 7, 9, 11 };   // 该数组的个数在初始化列表中计算得到
    int mappedVals[arraySize(keyVals)];   // 编译成功,说明通过 arraySize 能够在编译期得到数组大小
}

  将 arraySize 声明为 noexcept,是为了帮助编译器生成更好的目标码。

函数实参

除了数组型别退化为指针外,函数型别也会退化为函数指针,并且相关规则与数组型别推导一致。

当函数模板参数按值传递,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void someFunc(int, double)
{
    cout << "123";
}
 
template<typename T>
void func(T param)   // param 按值传递
{
    cout << type_name<decltype(param)>() << endl;
}
 
int main()
{
    func(someFunc);   // param 被推导为函数指针,具体型别是: void (*)(int, double)<br>}

当函数模板参数按引用传递,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void someFunc(int, double)
{
    cout << "123";
}template<typename T>
void func(T& param)   // param 按引用传递
{
    cout << type_name<decltype(param)>() << endl;
}
 
int main()
{
    func(someFunc);  // param 被推导为函数引用,具体型别是: void (&)(int, double)
}

  

posted @   皮卡啰  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示