18 | 应用可变模板和tuple的编译期技巧
18 | 应用可变模板和tuple的编译期技巧
讲述:吴咏炜
时长11:07大小10.16M
可变模板
转发用法
递归用法
tuple
数值预算
内容小结
课后思考
参考资料
赞 2
提建议
精选留言(25)
- 李亮亮2020-01-06N-->(N-1, N-1)-->(N-2, N-2, N-1)-->(1, 1 , 2 ....N-1)-->(0, 0, 1, 2...N-1)
作者回复: 😁 学得挺快。
5 - 泰伦卢2020-01-09compose那是完全没看懂唉,还有sequence那...
作者回复: 给个提示,到下面这个网站上看看模板是如何展开的: https://cppinsights.io/
4 - Geek_a16bbc2020-09-02用for loop來計算那256個count_bit()有什麼問題嗎?為什麼一定要在編譯期算好呢?
作者回复: 没什么一定。这是一个功能,是不是需要用取决于你的具体项目需求。文中只是个例子而已。 如果这个计算时间有点长,超过零点几秒,可能会影响程序启动的体验,也许你就需要预算。 如果其他静态初始化的对象需要用到用到这些数据,那预算也是最简单的方式。因为如果没有额外的代码的话,静态初始化的顺序在不同文件是没有保证的。 ……
2 - Geek_845be12020-12-24不用 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 对象必须在构造时完成初始化。
1 - Geek_a16bbc2020-09-05template <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 {};
1 - 宋强2020-03-16template <typename F> auto compose(F f) { return [f](auto&&... x) { return f( forward<decltype(x)>(x)...); }; } 老师,请问下auto&&... x没有出现在入参里,这个怎么产生呢?展开
作者回复: 这儿是返回一个函数对象啊。x是这个函数对象的参数。
1 - 禾桃2020-01-07template <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 成为其数据成员)。你可以试试看。我暂时没时间试验。
1 - 布拉姆2022-11-03 来自日本template<class F, class Tuple, size_t ...I> inline constexpr decltype(auto) apply_impl(F && f, Tuple && t, index_sequence<I...>) { return f( get<I>(forward<Tuple>(t)) ... ); } 这里对于index_sequence<I...>里面 I 有多少项目, 就展开多少项. 假如tuple一共2项, 则展开为: return f(std::get<0UL>(std::forward<std::pair<int, int> >(t)), std::get<1UL>(std::forward<std::pair<int, int> >(t))); 和下面原理一样: template <typename T, typename... Args> inline unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>(new T(forward<Args>(args)...)); } forward<Args>(args)... 会在编译时实际逐项展开 Args 和 args ,参数有多少项,展开后就是多少项。展开
- Marc Chan2022-03-12吴老师,因为我的工作年限比较短,刚满一年。看到第18讲,觉得这么复杂的模板,好像在工作中也没碰到过,就感觉有点离自己太远了的感觉。虽说慢慢看也能够看懂,但是就是会感觉到看完以后也没地方用。 请教一下这些内容往往会在什么地方用的多呢?或者说有没有一些更加适合初级C++ er 看的内容推荐呢?
作者回复: 最开始学 C++ 的,就找一本好的教材(如《C++ Primer 中文版》)。做练习,确保自己理解。 每一种高级技巧都有应用的场景,但有些应用需要有一定的实际的项目经验。模板是一种抽象机制,主要用在写库、搭框架这样的场景。相信随着项目经验的增加和架构能力的拓展,你会看到它们的用武之地。
- Peter2021-11-30怎么证明确实已经在编译期间已经计算过了呢?看反汇编吗?
作者回复: 看汇编是最终确认方式。不是反汇编。用命令行选项来产生汇编文件。 GCC/Clang 下用 -S,MSVC 下用 /Fa。
- 常振华2021-10-18auto bit_count = get_bit_count(make_index_sequence<256>()); <256>后面是不是多了一对圆括号()? make_index_sequence<256>展开之后,代入get_bit_count(index_sequence<V...>)模板,并没有一对圆括号()啊?
作者回复: make_index_sequence<256> 是一个类型,你不能用类型(比如 int)去调用一个函数的。必须生成一个对象。make_index_sequence<256>() 也可以写成 make_index_sequence<256>{},代表一个默认构造的该类型的对象。 如果换更“普通”的代码为例,那 foo(int) 这样函数原型,你是不能用 foo(int) 这样的写法去调用的。你可以用 foo(42),也可以用 foo(int{})——后者实际相当于 foo(int(0)),也相当于 foo(0)。
- 常振华2021-10-18为什么一会儿是大写的Tuple,一会儿又是小写的tuple,C++库里的模板不是小写tuple吗?
作者回复: 大写的 Tuple 不是小写的 tuple。 大写的 Tuple 代表一个抽象的类名称,你可以把它替换成为任何一个名称,比如全部改成 Tup 或者 T。但因为意图是 tuple,所以采用这种拼法。
- 宵练22332021-06-28贴一个也用到sequence的std::string enum编译期互转的例子 https://tao-fu.gitee.io/2020/11/09/C++%E6%9D%82%E8%B0%88/EnumStringConversion/
- chang2021-06-09吴老师,请问怎么查看某些代码是否在编译时计算的?比如以下代码(剽窃了Geek_845be1同学的),怎么确定get_bit_count<8>()是否在编译时计算的? #include <array> #include <iostream> using namespace std; constexpr std::size_t count_bits(std::size_t val) { if (!val) { return 0; } return 1 + count_bits(val & (val - 1)); } template <std::size_t N> constexpr std::array<std::size_t, N> get_bit_count() { std::array<std::size_t, N> counts{}; for (std::size_t i = 0; i < N; ++i) { counts[i] = count_bits(i); } return counts; } int main() { auto counts = get_bit_count<8>(); for (auto n : counts) { cout << n << " "; } cout << endl; }展开
作者回复: 最靠谱的方案是看汇编。另外,如果变量是 constexpr,那就只有编译时能求值编译才能过了。
- chang2021-06-09反复看着教程才把make_integer_sequence写出来(应该是半抄半写)。感觉这节已经把模板用得很偏了。个人认为若在现实项目中,最后一个bit_count的例子还是不要用模板好,为了节省运行时时间,却大大降低了代码的可读性及可维护性,不值当。
作者回复: 可读性也取决于读者的眼睛。这个例子确实运行时计算也没什么大不了,但类似的技巧还是有很多适用场合的。 我目前在项目的公用代码里大量使用此类技巧。不要求大部分开发者来读此类代码,他们会用就行了。 记住,C++的很多功能是给库开发者提供的,不是给应用开发者提供的。
1 - 林林2021-04-13老师,我写了一个能在编译器计算出N以内的素数列表的代码,但在编译时提示“C1202 递归类型或函数依赖项上下文太复杂” (N=1000) , 尝试过N=100时是可以编译成功的。 不知道有没有可以让N=1000也通过的办法(比如让编译器能递归更深的层)?
作者回复: 试试GCC?GCC的错误信息就会告诉你一个修改编译期递归深度的选项。其他就只能通过搜索引擎去找了。
共 3 条评论 - 某某某2021-01-31Tuple一下让我回到了vc6下写typelist的时代。但是现在的新代码反倒是看不太懂了,bit_count_t里面最后调用count_bits后的...是如何展开的。
作者回复: 你可以自己看一下工具展开的结果: https://cppinsights.io/s/704e8a55
- 闲着也是贤者2020-07-31老师, auto squared_sum = compose(sum_list, square_list);将这两个实参的位置替换后就不能工作呢,这是为什么呢?应该能正常工作,但是却出错了?
作者回复: sum_list 返回单一数值,不满足 square_list 的参数要求。
- 中山浪子2020-07-10写过一些模版,公司代码也涉及到模版,看了老师的模版代码以后,才发现自己还是不懂模版
作者回复: 模板有简单的用法,也有复杂的用法。简单的用法有时更加实用,也比较容易维护,不容易出问题。
- 吃鱼2020-06-03只能大概看懂,最后的例子想尝试编译一下,结果 auto bit_count = get_bit_count(make_index_sequence<256>()); 这一句报错了 匹配不到模板,读到这么晚好伤心。。。
作者回复: 什么编译器,什么错误?按我要求的编译器和选项,都是可以通过的…… 另外,如果自己输入的话,也有出现小错误的可能。可以看一下我放在 GitHub 上的完整示例: https://github.com/adah1972/geek_time_cpp/tree/master/18