C++ 模板
Published on 2024-11-17 16:50 in 分类: C-CPP with dutrmp19
分类: C-CPP

C++ 模板

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

如果您有任何关于文章的建议,欢迎评论或在 GitHub 提 PR

作者:dutrmp19
本文为作者原创,转载请在 文章开头 注明出处:https://www.cnblogs.com/dutrmp19/p/18550730
遵循 CC 4.0 BY-SA 版权协议


posted @   dutrmp19  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示