首页 > 编程语言 >C++ 模板

C++ 模板

时间:2024-11-17 16:40:05浏览次数:1  
标签:std 函数 C++ template foo 模板 cout

C++ 模板

  • Created: 2024-03-24T20:24+08:00
  • Published: 2024-11-17T16:37+08:00
  • Categories: CPP

目录

在模板中,我们可以声明三种类型的形参(Parameters),分别是:

  1. 非类型模板形参(Non-type Template Parameters)
  2. 类型模板形参(Type Template Parameters)
  3. 模板模板形参(Template Template Parameters)

——C++模板元编程(零):前言 - 知乎 的笔记,但是原文有些地方还是不太明白。

也就是说,定义模板的时候,参数不一定要写作 typename T,还可以直接写 int 之类的,如 my_template<5>()

偏特化

偏特化的意思是:当传入的类型参数为 T 时候,我希望有一个自己重写的实现,如:

#include <iostream>
template <typename T>
void print(T x)
{
    std::cout << x << std::endl;
}

template <>
void print<int>(int x)
{
    std::cout << "print(" << x << ")" << std::endl;
}

template <>
void print(double x)
{
    std::cout << "print(" << x << ")" << std::endl;
}

int main()
{
    print(1);
    print(1.1);
}

类模板

类模板基础的用法略过,类模板 C++11 不支持参数推导,所以使用时候一定要提供模板参数。
它的神奇之处在于:

  1. 支持偏特化
  2. 支持引用的偏特化,引用类型偏特化的语法也是独树一格:
// example from https://zhuanlan.zhihu.com/p/384826036
template <typename T> struct is_reference      { static constexpr bool value = false; };    // #1
template <typename T> struct is_reference<T&>  { static constexpr bool value = true; };     // #2
template <typename T> struct is_reference<T&&> { static constexpr bool value = true; };     // #3

std::cout << is_reference<int>::value << std::endl;    // 0
std::cout << is_reference<int&>::value << std::endl;   // 1
std::cout << is_reference<int&&>::value << std::endl;  // 1

更多用法参考:C++模板元编程(三):从简单案例中学习 - 知乎

函数模板

首先要破除一些关于函数模板的迷思:

  1. 函数模板的模板参数虽然可以通过函数的参数推导,但是也可以直接指定,像 foo<int>() 就是在直接指定,并非语法错误。
  2. 函数模板只支持全特化。所以特化的模板一定要依赖于主模板,语法是把 template<typename T, ...> 中尖括号 <>所有的模板参数都列出来,一个都不能少。

函数和函数模板可以放在一起重载,看起来很烧脑,
那么发生了一次函数调用,如何确定使用哪一个函数呢?按照我的理解:

  1. 函数调用时候,如果提供了模板参数列表,则显式实例化,如 foo<int>(1) 提供了 <int>
    1. 先查找是否有特化的函数模板,类似这个格式:template<> foo<int>()
    2. 没有就找函数主模板
  2. 如果没有提供模板的参数列表,则说明需要根据函数参数推导类型
  3. 从全特化的模板中查找符合条件的
  4. 找不到就用主模板

严谨表述参考:C++模板元编程(四):深入模板 - 知乎

template<typename T>
void bar(T v) {
    std::cout << "1 template param" << std::endl;
    return;
}

template<>
void bar(int v) {
    std::cout << "1 template param specialization" << std::endl;
    return;
}

template<typename T, typename U>
void bar(T v) {
    std::cout << "2 template param" << std::endl;
    return;
}

int main()
{
    bar<int, float>(1);     // 2 template param
    bar<float>(1);          // 1 template param
    bar(1);                 // 1 template param specialization
    return 0;
}

bar<int, float> 显式提供了模板参数,故实例化第三个 bar
bar<float> 也显式提供了模板参数,确定类型 T 为 float,并且没有找到对应的特换版本的函数,故实例化第一个
bar(1) 只提供了一个函数参数,只需要检查函数签名,对于第三个模板,无法推导出类型 U;使用第二个模板。

函数模板 | 现代 C++ 模板教程

形参包

形参包 (C++11 起) - cppreference.com

  • 语法:我的记忆方法是,把 ... 写在类型和包名中间,这样可以避免忘记写 ...
    template<typename ...Args>
    print(const Args& ...args) {
      int _[]{ (std::cout << args << ' ' ,0)... };
    }
    
  • 包展开:和中文语法下省略号的用法一致

Question

我记不住模板的语法,尤其是偏特化的语法,怎么办?

以 remove_reference 为例:

template <typename T> // line 1
struct remove_ref
{
    typedef T type;
};

template <typename T> // line 1
struct remove_ref<T &>
{
    typedef T type;
};

让我们忽略 template 那一行,可以得到如下代码:

struct remove_ref // primary template does not need args
{
    typedef T type;
};


struct remove_ref<T &&> // specialization
{
    typedef T type;
};
  1. 主模板不需要写 <args...>
  2. specialization 写上自己需要实例化的时候,对应主模板的形式,最后在第一行补上未知的类型参数

template 那一行表示的是,下面要用到的未知的类型参数。

面试问题

利用类模板和函数模板实现编译器计算斐波那契数列

./template-fibo.cpp

注意:类模板成员变量要用 static const 修饰

  • static 允许 fib<5>::value 这样形式访问
  • const static 允许编译器进行计算

模板的声明和定义为什么不能分开写,要想分开写该怎么做

因为是 C++ 是分离式编译,不会递归查找自己需要的函数。

// foo.h
template<typename T>
void foo(T a);

// foo.cpp
template<typename T>
void foo(T a) {
    std::cout << a << std::endl;
}

// main.cpp
#include"foo.h"
int main() {
    foo<int>();
}

编译到 main.cppfoo<int> 时候,main.o 里会有一个 call foo_int 的指令,等待链接的时候补上。
但是编译到 foo.cpp 的时候,他不知道 main.o 里面调用了 foo<int>,所以不会实例化 foo_int 这个函数。

可以直接把 foo.hfoo.cpp 合并到一个文件解决这个问题,叫做 hpp
stackoverflow 上有两种方法

  1. foo.h 最后加上一行 #include"foo.cpp",也就是合并到一块写。我看来不是从根本上解决这个问题。

  2. 问题的关键是,分离式编译的时候,如何让 foo.cpp 得知需要实例化 foo<int>,修改为:

    // foo.cpp
    template<typename T>
    void foo(T a) {
        std::cout << a << std::endl;
    }
    
    template void foo<int>; // tell the compiler to instantiation this function
    

标签:std,函数,C++,template,foo,模板,cout
From: https://www.cnblogs.com/dutrmp19/p/18550730

相关文章

  • c++入门基础后续
    1.缺省参数缺省参数是指在声明或定义的同时给上指定的一个缺省值,在调用函数是如果没有传指定的实参那么就会用这个缺省值。缺省参数分为全缺省和半缺省。全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃......
  • C++仍要用的scanf函数介绍
    很多C++初学者刚学便是使用cin和cout,这无可厚非;但C语言中的scanf函数在一些特定情况下仍必不可少,故写给C++初学=者scanf函数的部分介绍以及具体使用情景。scanf输入数字的用法头文件:#include<cstdio> 调用格式:scanf(格式控制字符串,变量地址列表);注:格式说明和各输入......
  • CSP/信奥赛C++语法基础刷题训练(11):洛谷P5743:猴子吃桃
    CSP/信奥赛C++语法基础刷题训练(11):洛谷P5743:猴子吃桃题目描述一只小猴买了若干个桃子。第一天他刚好吃了这些桃子的一半,又贪嘴多吃了一个;接下来的每一天它都会吃剩余的桃子的一半外加一个。第n......
  • CSP/信奥赛C++语法基础刷题训练(12):洛谷P1047:[NOIP2005 普及组] 校门外的树
    CSP/信奥赛C++语法基础刷题训练(12):洛谷P1047:[NOIP2005普及组]校门外的树题目描述某校大门外长度为lll的马路上有一排树,每两棵相邻的树之间的间隔都是......
  • 【华为OD技术面试手撕真题】84、前 K 个高频元素 | 手撕真题+思路参考+代码解析(C & C+
    文章目录一、题目......
  • C++因子个数
    目录1.题目描述2.输入输出3.样例输入 Copy样例输出 Copy1.题目描述一个自然数N的正因子个数记为F(N),例如18的所有正因子为1、2、3、6、9、18,所以F(18)=6。现在给出K,求所有满足F(N)=K的N中最小的数。2.输入从文件读入数据,第一行为K,其中0<K≤80。输出......
  • 【C++】引用
    目录引用的概念引用的特点引用定义时必须初始化引用的类型必须与被引用对象的类型相同一个变量可以设置多个引用引用只能对应一个实体引用有传递性赋值与引用的区别引用作参数引用作参数-利用引用的变量共同指向同一块内存空间交换两个变量传递单链表节点指针引......
  • GESP2023年12月认证C++四级( 第一部分选择题(11-15))
    ......
  • GESP2023年12月认证C++四级( 第二部分判断题(1-5))
    ......
  • cmake系列-怎么在构建C++库文件时动态的选择构建动态库还是静态库
    在之前我们介绍的内容里,关于构建动态库还是静态库都是在CMakeLists.txt里指定的,那如果一个解决方案原来是构建动态库,然后因为某些原因又希望构建静态库了,那岂不是还要修改CMakeLists.txt,对于平时用的构建系统来说好像还真的是需要修改,哈哈,但是cmake确实有方案能够在不用修改......