13 | 编译期能做些什么?一个完整的计算世界
13 | 编译期能做些什么?一个完整的计算世界
讲述:吴咏炜
时长12:47大小11.68M
编译期计算
编译期类型推导
简易写法
通用的 fmap 函数模板
内容小结
课后思考
参考资料
赞 7
提建议
精选留言(28)
- 禾桃2019-12-25脑壳儿疼的兄弟姐妹们,我这有个小偏方, 哈哈 While< Sum<2>::type >::type::value 实例化(instantiation)过程 --> While< SumLoop<0, 2> >::type::value --> WhileLoop<SumLoop<0, 2>::cond_value, SumLoop<0, 2>>::type::value --> WhileLoop<true, SumLoop<0, 2>>::type::value --> WhileLoop<SumLoop<0, 2>::cond_value, SumLoop<0, 2>::next_type>::type::value --> WhileLoop<true, SumLoop<2, 1>>::type::value --> WhileLoop<SumLoop<2, 1>::cond_value, SumLoop<2, 1>::next_type>::type::value --> WhileLoop<true, SumLoop<3, 0>>::type::value --> WhileLoop<SumLoop<3, 0>::cond_value, SumLoop<3, 0>::next_type>::type::value --> WhileLoop<false, SumLoop<3, -1>>::type::value --> SumLoop<3, -1>::res_type::value -->integral_constant<int, 3>::value -->3展开
作者回复: 对,对于模板,就是要在脑子里或纸上、电脑上把它展开……☺️
12 - 总统老唐2019-12-25记得吴老师之前预告过,这一节可能会比较难,确实被难住了。在第一个 If 模板这里就被卡住了,老师能给个简单的例子来说明这个 If 模板该如何使用么?
作者回复: 下面的函数和模板是基本等价的: int foo(int n) { if (n == 2 || n == 3 || n == 5) { return 1; } else { return 2; } } template <int n> struct Foo { typedef typename If< (n == 2 || n == 3 || n == 5), integral_constant<int, 1>, integral_constant<int, 2>>::type type; }; 你可以输出 foo(3),也可以输出 Foo<3>::type::value。
8 - 莫言2021-12-27template < template <typename, typename> class OutContainer = vector, typename F, class R> 请问老师,这个OutConContainer前面的template<typename,typename>应该怎么理解
作者回复: 模板的参数可以是类型(如 int),可以是值(如长度 8),也可以是其他(类或别名)模板。这个语法代表 OutContainer 是一个带两个类型模板参数的(类或别名)模板。(vector 有两个类型模板参数,第二个有默认值,一般不需要自己提供。)
2 - chang2021-06-03比如,如果我们对 const string& 应用 remove_const,就会得到 string&,即,remove_const::type 等价于 string&。 remove_const只能去顶层const。const string&应用remove_const后还是const string&,const string应用remove_const后是string。
作者回复: 谢谢抓虫🙏。
1 - 鲁·本2020-10-11对While<Sum<10>::type>::type::value进行手动推导,最终是能推导出实际语句是 integral_constant<int,10+9+...1>::value的,但让我独立写出 完整的代码是万万不能的😄
作者回复: 哈哈,我写的时候头也很大。只是为了说明能写出来,而不是真想/需要这么写。
1 - 吃鱼2020-06-02“如果要得到布尔值的话,当然使用 `is_trivially_destructible::value` 就可以,但此处不需要。需要的是,使用 `()` 调用该类型的构造函数,让编译器根据数值类型来选择合适的重载。这样,在优化编译的情况下,编译器可以把不需要的析构操作彻底全部删除。” 老师,这里没太懂,使用 `()` 调用该类型的构造,这里的调用是在哪里调用,为什么 destroy 要调用构造函数
作者回复: 指的就是这句: _destroy(ptr, is_trivially_destructible<T>()) 这儿构造了一个 true_type 或 false_type 的对象,然后编译器会根据第二个参数的类型,决定调用 _destroy 的哪个重载。
共 3 条评论1 - YouCompleteMe2020-01-16template <typename Body> struct whileLoop<true, Body> 部分似乎改成下面这样,更直观, 编译时计算更少呢 template <typename Body> struct whileLoop<true, Body> { typedef typename whileLoop< Body::next_type::cond_value, typename Body::next_type>::type type; };展开
作者回复: 嗯,是的,你的写法能少展开一次。👍
1 - 吃一个芒果2020-01-02template <int num1, int num2> struct Add_ { const static int res = num1 + num2; }; template <int num1, int num2> struct Sub_ { const static int res = num1 - num2; }; template <bool Condition> struct If_; template <> struct If_ <true> { template<int num1, int num2> using type = Add_<num1, num2>; }; template <> struct If_ <false> { template<int num1, int num2> using type = Sub_<num1, num2>; }; template<int num1, int num2> template<bool Condition> using If = typename If_<Condition>::template type<num1, num2>; template<int num1, int num2> using True = If<true>; template<int num1, int num2> using False = If<false>; 老师你好,我想问一个语法方面可能比较钻牛角尖的问题 我定义了If_,用来在true和false的时候返回不同的模板。又定义了True和False,这样就可以通过True<a, b>::res或者False<a, b>::res来获取不同模板的计算结果。但是如果我想用类似If<condition><a, b>::res这样的调用(不知道我表达清楚没有)来获取不同的结果(在不改If_定义的情况下),应该怎么写呢?谢谢!展开
作者回复: 你希望的写法我觉得是写不出来的,而且意义似乎也不大?下面这样写似乎表达能力是一样的,并且可以过: template <bool Condition, int num1, int num2> using If = typename If_<Condition>:: template type<num1, num2>; template <int num1, int num2> using True = If<true, num1, num2>; template <int num1, int num2> using False = If<false, num1, num2>;
1 - 总统老唐2019-12-27吴老师,关于最后这个例子,有两个小问题: 1,我们平时定义一个 vector 的时候,一般并不会写成 vector<int, allocator<int>> vec 这种形式,为什么模板函数里面定义返回值 result 时,需要多一个 allocator? 2,fmap函数的入参和for循环,全都用的右值引用,有什么特殊考量么?
作者回复: 1. 因为模板的定义就是这个样子,虽然我们平时第二个参数用的都是默认模板参数。 2. 不是右值引用,是转发引用。复习一下第 3 讲结尾部分吧。
1 - 陈狄2022-04-29编译期编程确实6,模版写循环确实绕,展开如下: While<Sum<10>::type>::type::value While<SumLoop<0, 10>>::type::value WhileLoop<SumLoop<0, 10>::cond_value, SumLoop<0, 10>>::type::value WhileLoop<true, SumLoop<0, 10>>::type::value WhileLoop<SumLoop<0, 10>::cond_value, SumLoop<0, 10>::next_type>::type::value WhileLoop<true, SumLoop<10, 9>>::type::value WhileLoop<SumLoop<10, 9>>::cond_value,SumLoop<10, 9>>::next_type>::type::value ... WhileLoop<SumLoop<55, 0>>::cond_value,SumLoop<55, 0>>::next_type>::type::value WhileLoop<false,SumLoop<55, -1>>::type::value SumLoop<55, -1>::res_type::value integral_constant<int, 55>::value展开
作者回复: 我自己写完都不想看这代码了……只是想练习一下确实可以写出循环。
- Geek_15f2c92021-04-28老师,对万能引用使用完美转发是否好点,result.push_back(std::forward<decltype(f(item))>(f(item)))或result.emplace_back(std::forward<decltype(f(item))>(f(item)));;
作者回复: 很好的问题。但是,实际必要性不高。 原因是,f(…) 极少会有合法的、返回右值引用的场景。唯一我能想到的可能性是返回值是根据输入的右值生成的情况。这样的话,得保证在输入是个右值容器时传递元素的右值给 f,所以真要考虑这种情况的话,代码比你写的还要再复杂不少……
- 王旧业2021-02-17While<Sum<10>::type>::type::value的例子中会使用到了自己integral_constant模板,但是stl中也有一份integral_constant,所以运行本示例代码时如果一开始直接 using namespace std会编译报错
作者回复: 我给出的定义只是给大家直观地看一下。生产代码应该使用标准库。 不过,生产代码一般也不建议使用 using namespace std; 哦。
- Gazelle2020-11-29我实践写了下remove_const,好像没有把const去掉。这里是不是有点问题呢? https://stackoverflow.com/questions/15887144/stdremove-const-with-const-references 我看这里说是如果同时有const和引用的话,还需要去掉引用? std::remove_const<std::remove_reference<const string&>::type>::type
作者回复: 这里有个小细节:const T& 等同于 T const&,但和 T& const 不同。前者是一个指向常量的引用,后者是一个常引用。只有后者才被看作是一个“常量”。
- talor2020-08-07脑子烧坏了
作者回复: 是有点烧。搞明白了就是个新世界。
- Geek_68d3d22020-08-04typedef SumLoop<0, n> type;这个语法不是给类型定义别名吗 怎么代码里变成了变量定义???
作者回复: 还是类型。但在模板元编程里,玩的就是利用类型推导来做计算。所以类型是当成变量来用的。 哈哈,有点绕,慢慢看。😄
共 2 条评论 - zhengfan2020-07-07这一课看的脑浆都沸腾了…… 吴老师,您好。有如下几个问题请教一下: 在SumLoop中定义的res_value的目的是什么?为什么不直接使用 typedef integral_constant<int, result> res_type 而是 typedef integral_constant<int, res_value> res_type呢?前者编译执行都没有问题的。 在integral_constant定义typedef integral_constant type目的是什么? 另外我尝试稍微改动了一下文中例子如下: template <class T, T v> struct type_constant { static const T value = v; typedef T value_type; typedef type_constant type; }; template <typename T, T result, T n> struct SumLoop { static const bool cond_value = (n > (T)0); // static const int res_value = result; typedef type_constant<T, result> res_type; typedef SumLoop<T, result + n, n - (T)1> next_type; }; template <typename T, T n> struct Sum { typedef SumLoop<T, (T)0, n> type; }; 结果发现只能够支持范整型类型如int,size_t,ulong等等: While<Sum<size_t, 10>::type>::type::value编译执行工作正常;但对于浮点型如: While<Sum<float, 10.0)>::type>::type::value编译报错: error: ‘float’ is not a valid type for a template non-type parameter 请问这是什么原因?展开
作者回复: 对我来讲,res_type 和 res_value 是同一个值的两种不同表现方式。目前这种写法,于我更为自然些。你的写法也没有问题。 浮点数由于精度问题,目前在 C++ 里不允许用作模板参数。可以参考这个讨论 https://stackoverflow.com/questions/2183087/why-cant-i-use-float-value-as-a-template-parameter
- 范闲2020-03-31最近在看STl源码分析。 1.一直再想allocator类为什么要存在? 为不同的容器类型提供了内存资源管理类,同时也支持用户自定义,更方便和灵活。实际上对于容器而言内部的资源都是分布在对上的,栈上的只不过是个符号。 2.为什么要有type_traits? iterator是对容器类的一种底层抽象.type_traits实际上也是这样的抽象,只不过更抽象。展开
作者回复: 1. 确实是为了灵活,但这个设计不能算很成功,因为真的用的人非常少。但从理论上来讲,你用 allocator 可以实现非常特别的内存管理策略,如预分配之类。 2. 迭代器实际上是比容器更抽象的概念,第 7 讲介绍的 istream_line_reader 就不能说是个容器。第 29 讲有比容器更抽象的“范围”。type_traits 翻译成中文是“类型特点”,是各种对共性的描述,是一组非常散的概念,跟迭代器、容器之类相提并论我觉得不妥。
- czh2020-02-03类比刚学函数时的感觉。模板类型推导=函数嵌套,用于提供逻辑过程;计算过程由参数传递的过程中进行。逻辑+计算,这样就完成了模板计算。
- oozero2020-02-03老师可以将文中示例代码片段整理成文件,分享到GitHub,让c++基础不扎实的同学可以更直观的学习吗?
作者回复: 那要花时间整理了……后半部分的代码我比较全点,前半部分一开始没有保留所有的测试代码……
- 光城~兴2020-01-05在文中提到这么一句话:“实际上,到现在为止,我们讲的东西还没有离开 C++98。而我们下面几讲里很快就会讲到,如何在现代 C++ 里不使用这种麻烦的方式也能达到同样的效果。” 想问一下如果用现代C++实现上述的IF或者WhileLoop,究竟跟上述的有什么区别。
作者回复: 没区别。 要点是你不需要使用这些模板,也同样能达到编译期编程的效果。