现代 C++ 编程实战
吴咏炜
前 Intel 资深软件架构师
34196 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
加餐 (1讲)
现代 C++ 编程实战
15
15
1.0x
00:00/00:00
登录|注册

18 | 应用可变模板和tuple的编译期技巧

make_index_sequence
apply_impl函数
tuple_size_v
get函数
tuple的成员数量
compose函数示例
sum函数示例
forward(args)展开
make_unique示例
Function composition
std::apply
std::integer_sequence
std::tuple
Parameter pack
get_bit_count函数
bit_count_t模板
count_bits函数
apply函数
基本用法
递归用法
转发用法
吴咏炜
Wikipedia
cppreference.com
课后思考
内容小结
数值预算
tuple
可变模板
参考资料
应用可变模板和tuple的编译期技巧

该思维导图由 AI 生成,仅供参考

你好,我是吴咏炜。
今天我们讲一个特殊的专题,如何使用可变模板和 tuple 来完成一些常见的功能,尤其是编译期计算。

可变模板

可变模板 [1] 是 C++11 引入的一项新功能,使我们可以在模板参数里表达不定个数和类型的参数。从实际的角度,它有两个明显的用途:
用于在通用工具模板中转发参数到另外一个函数
用于在递归的模板中表达通用的情况(另外会有至少一个模板特化来表达边界情况)
我们下面就来分开讨论一下。

转发用法

以标准库里的 make_unique 为例,它的定义差不多是下面这个样子:
template <typename T,
typename... Args>
inline unique_ptr<T>
make_unique(Args&&... args)
{
return unique_ptr<T>(
new T(forward<Args>(args)...));
}
这样,它就可以把传递给自己的全部参数转发到模板参数类的构造函数上去。注意,在这种情况下,我们通常会使用 std::forward,确保参数转发时仍然保持正确的左值或右值引用类型。
稍微解释一下上面三处出现的 ...
typename... Args 声明了一系列的类型——class...typename... 表示后面的标识符代表了一系列的类型。
Args&&... args 声明了一系列的形参 args,其类型是 Args&&
forward<Args>(args)... 会在编译时实际逐项展开 Argsargs ,参数有多少项,展开后就是多少项。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了如何利用可变模板和tuple进行编译期计算,以及实现函数的组合。首先,介绍了可变模板的转发用法和递归用法,并提供了示例代码展示了如何利用可变模板参数转发到模板参数类的构造函数上,以及如何实现编译期递归和函数的组合。其次,详细介绍了tuple的基本用法,包括成员数量的确定、get函数的使用以及tuple_size_v的应用。然后,讨论了如何利用index_sequence实现编译期展开tuple的成员,并展示了apply函数的简化版本。接着,通过实际项目的例子,演示了如何利用constexpr函数和index_sequence来在编译期计算二进制数中1比特的数量,并利用bit_count_t模板和make_index_sequence实现了对256种情况的计数值记录。最后,总结了可变模板、tuple和index_sequence的重要性,以及它们在编译期计算中的应用。整体而言,本文深入浅出地介绍了如何利用C++的特性进行编译期计算和函数组合,为读者提供了丰富的实例和思考题,有助于提高代码的灵活性和可复用性。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《现代 C++ 编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(27)

  • 最新
  • 精选
  • 李亮亮
    N-->(N-1, N-1)-->(N-2, N-2, N-1)-->(1, 1 , 2 ....N-1)-->(0, 0, 1, 2...N-1)

    作者回复: 😁 学得挺快。

    2020-01-06
    7
  • 泰伦卢
    compose那是完全没看懂唉,还有sequence那...

    作者回复: 给个提示,到下面这个网站上看看模板是如何展开的: https://cppinsights.io/

    2020-01-09
    4
  • Geek_845be1
    不用 index_sequence 来初始化 bit_count: ``` consteval int count_bits(unsigned char val) { if (val == 0) return 0; return (val & 1) + count_bits(val >> 1); } template <std::size_t N> consteval std::array<unsigned char, N> get_bit_count() { std::array<unsigned char, N> tbl{}; for (auto i = 0; i != N; ++i) tbl[i] = count_bits(i); return tbl; } ```

    作者回复: 很好。 两个细节注意一下。 1. consteval 是 C++20 的语法。在 C++17 里,我们仍然只能用 constexpr。 2. 在 C++20 里,tbl{} 是可以写成 tbl 的。但在 C++17 里不可以,标准要求所有的 constexpr 对象必须在构造时完成初始化。

    2020-12-24
    3
  • Geek_a16bbc
    用for loop來計算那256個count_bit()有什麼問題嗎?為什麼一定要在編譯期算好呢?

    作者回复: 没什么一定。这是一个功能,是不是需要用取决于你的具体项目需求。文中只是个例子而已。 如果这个计算时间有点长,超过零点几秒,可能会影响程序启动的体验,也许你就需要预算。 如果其他静态初始化的对象需要用到用到这些数据,那预算也是最简单的方式。因为如果没有额外的代码的话,静态初始化的顺序在不同文件是没有保证的。 ……

    2020-09-02
    2
  • 李云龙
    老师,我尝试写了make_integer_sequence,没有看专栏末尾的答案,您看下这段代码满足您思考题的要求吗?有不正确的地方,烦请老师指正。 #include <iostream> #include <utility> #include <tuple> #include <type_traits> template <class T, T... Ints> struct integer_sequence {}; template <size_t N, class T, T... Ints> struct integer_sequence_helper { typedef typename integer_sequence_helper<N - 1, T, N - 1, Ints...>::type type; }; template <class T, T... Ints> struct integer_sequence_helper<0, T, Ints...> { typedef integer_sequence<T, Ints...> type; }; template <size_t N, class T> using make_integer_sequence = typename integer_sequence_helper<N, T>::type; template <class F, class Tuple, class T, T... I> constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, integer_sequence<T, I...>) { return f(std::get<I>(std::forward<Tuple>(t))...); } template <class F, class Tuple> constexpr decltype(auto) apply(F&& f, Tuple&& t) { return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), make_integer_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>, size_t>{}); } int main() { auto func = [](int x, int y, int z) { std::cout << x << ", " << y << ", " << z << std::endl; }; std::tuple<int, int, int> t{ 1,2,3 }; apply(func, t); return 0; }

    作者回复: 你这边取巧了一下,用 size_t N 这种写法,规避了一个技术问题,不过,似乎也算是一种解决方案。(思考题答案里用的是 T N,写起来就更麻烦了。) 代码本身基本没问题。不过,在我测试的时候,我的环境里你的 apply 和标准库的 std::apply 发生了冲突,测你的代码要把 apply 改成 ::apply 才行。

    2023-10-24归属地:北京
    1
  • chang
    反复看着教程才把make_integer_sequence写出来(应该是半抄半写)。感觉这节已经把模板用得很偏了。个人认为若在现实项目中,最后一个bit_count的例子还是不要用模板好,为了节省运行时时间,却大大降低了代码的可读性及可维护性,不值当。

    作者回复: 可读性也取决于读者的眼睛。这个例子确实运行时计算也没什么大不了,但类似的技巧还是有很多适用场合的。 我目前在项目的公用代码里大量使用此类技巧。不要求大部分开发者来读此类代码,他们会用就行了。 记住,C++的很多功能是给库开发者提供的,不是给应用开发者提供的。

    2021-06-09
    1
  • Geek_a16bbc
    template <class T, T... Ints> struct integer_sequence {}; 為什麼需要class T?不能template<T... Ints>? template <size_t... Ints> using index_sequence = integer_sequence<size_t, Ints...>; 同樣的,這裡可以寫成integer_sequence<Ints...>?

    作者回复: 第一种写法,不可以。你不可以在不声明 T 的情况下,突然蹦出来一个 T。 第二种写法,也不可以,因为这是 integer_sequence 定义的模板形式。不过,你确实可以独立定义一个类似下面形式的模板,不使用 integer_sequence: template <size_t... Ints> struct index_sequence {};

    2020-09-05
    1
  • 中山浪子
    写过一些模版,公司代码也涉及到模版,看了老师的模版代码以后,才发现自己还是不懂模版

    作者回复: 模板有简单的用法,也有复杂的用法。简单的用法有时更加实用,也比较容易维护,不容易出问题。

    2020-07-10
    1
  • 宋强
    template <typename F> auto compose(F f) { return [f](auto&&... x) { return f( forward<decltype(x)>(x)...); }; } 老师,请问下auto&&... x没有出现在入参里,这个怎么产生呢?

    作者回复: 这儿是返回一个函数对象啊。x是这个函数对象的参数。

    2020-03-16
    1
  • 禾桃
    template <typename F> auto compose(F f) { return [f](auto&&... x) { return f(x...); }; } 貌似用compiler(gcc version 4.8.5 20150623) 就会遇到下面编译错误 // In function ‘auto compose(F)’: // error: expansion pattern ‘auto&&’ contains no argument packs // return [f](auto&&... x) { 用compiler(gcc version 8.3.1 20190311)就不会有问题。 如果公司目前只允许用(gcc version 4.8.5 20150623),请问有什么workaround? 谢谢!

    作者回复: --- 更新 --- 我费了九牛二虎之力,终于把例子改得能在 gcc 4.8 下工作了。我觉得你不想维护这样的代码的。 这儿空间不够。我放在这里: http://wyw.dcweb.cn/download.asp?path=&file=jike_18_gcc48.cpp 我觉得升级 GCC 绝对是更好的主意。 --- 原回复 --- 没啥好办法……泛型lambda至少要求gcc 4.9。只能不用这类功能了。 手写一个函数对象模板也许可以完成这个功能(让 f 成为其数据成员)。你可以试试看。我暂时没时间试验。

    2020-01-07
    1
收起评论
显示
设置
留言
27
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部