C++现代模板元编程

个人发现很多国外的大佬的演讲或者文章都很不错,但是鲜有人来进行分享,届后本人会时不时拿一些看起来很好的东西来给大家分享,主要也是搬运,不过也省去了大家去读英文的麻烦,同时文章中也会参杂着一些自己的见解。
本文来自于CppCon 2014: Walter E. Brown的一篇演讲 Modern Template Metaprogramming(现代模板元编程),我尽可能对这篇演讲进行还原。演讲通过比较多的例子的挨个讲述使得听众可以充分的理解元编程的奥秘,演讲非常精彩,希望我的传达可以有所体现。
如有需要可以关注微信公众号:0号程序员

元编程

元编程的意思是用来产生新代码的代码,而且新产生的代码实现了我们真正想要的功能,通常名词“元编程”暗示了一种自反的属性。演讲中选取的例子也基本来自于标准库,方便理解和了解标准库。
演讲花了一些篇幅做自我介绍,同时介绍了作者的一些共享,大家感兴趣可以去了解,这里不展开说。
C++的模板元编程是使用模板实例化驱动的编译期编程的方式,这样可以提升代码灵活性及运行期的性能。那也就是意味着不存在可变性,不存在虚函数,不存在rtti等特性。
以下讲述中的元函数可以分为值元函数类型元函数两种。

使用以struct为基础封装元函数

template<int N>
struct abs {static_assert(N != INT_MIN);static constexpr int value = (N < 0) ? -N : N;
};// call
int const n = 12;
abs<n>::value;

展示了一个编译期求绝对值的元函数的例子,首先接受模版参数是非类型的,要保证N数值能够在编译期确定,然后使用静态断言保护N不为最小值,因为int的最小值无法取得他的相反数,(int的取值范围是-2^31 ~ 2^31-1) 。最后使用value存放到N的绝对值。
使用constexpr函数来改写一个这个元函数:

constexpr int abs(int N) { return (N < 0) ? -N : N; } 

这样做的好处是可以像调用正常函数那样调用一个元函数,更加符合人类的思维,但是使用struct封装的元函数能够提供比较多的工具使用,比如说一些公开成员的类型声明(using/typedef形容),公开的成员数据声明(static const/constexpr形容),这个是constexpr函数做不到的。
这里其实就是类型的萃取(类型元函数)已经值元函数的封装了,且这两个可以兼顾。除此之外还可以公开成员模板,比如上边例子的static_assert。
以上特性会非常有用,以gcd(最大公约数)为例:

template<unsigned M, unsigned N>
struct gcd {static int const value = gcd<N, M % N>::value;
};template<unsigned M>
struct gcd<M, 0> {static_assert(M != 0); // gcd(0,0) undefinedstatic int const value = M;
};

使用编译期递归及模板的偏特化来实现,使用static_assert保护M不为0,使用static int const value来存放最终的取值。这里本人来看其实目前c++的constexpr元函数也可以做到,不过上边所说的提供的工具等确实是无法替代的。的确标准库的实现也都是基于此(struct封装)来做到的。

元函数概述

以类型为参数的元函数

之前所比较常见的则是sizeof,sizeof以类型为参数,可以知道该类型所占内存的大小。
但是我们也可以使用类型为参数来写我们的代码,以求一个数组的层级(维度)为例:

template<class T>
struct rank {static size_t const value = 0;
};template<class U, size_t N>
struct rank<U[N]> {static size_t const value = 1u + rank<U>::value;
};// call
using array_t = int[10][20][30];
rank1<array_t>::value // 3

brown这里讲述了一个方法论,一般这里需要两部分,一部分是比较常规的或者递归的结束条件,也就是上边中的主模板;另一部分就是所谓正常处理实现,也就是上边的模板特化部分。
这里计算数组的层级中,将最后一维使用N特化出来,这样U就可以表示前几维,如此就是U的维数加1就可以递归获得维度。

以类型为结果的元函数

可以产出类型作为结果,比如说移除掉类型的const修饰符:

template<class T>
struct remove_const {using type = T;
};template<class U>
struct remove_const<U const> {using type = U;
};// call
remove_const<T>::type t;

remove_const可以做到移除掉类型的const,如上主模板比较通用的,输入任何类型都返回该类型,特化的这个模板实现比较巧妙,仅仅接收const修饰的类型为入参,然后正好返回去掉了const的类型。

C++11标准库元函数惯例1

C++标准库中一般来说如果是以类型为结果的话,都是用type来给它命名,算是一个约定。介于一些历史原因,标准库中的类型结果不都是以type命名,比如说到的iterator_traits中。

type_is

然后brown说到元函数type_is,他说到这是一个比较特殊的元函数:

template<class T>
struct type_is {using type = T;
};

我们看起来平平无奇,仅仅是把入参直接作为结果,但这也是他的特殊之处。在其他元函数对其继承时出奇的好用:

template<class T>
struct remove_volatile : type_is<T> {
};template<class U>
struct remove_volatile<U volatile> : type_is<U> {
};

remove_volatile类似于remove_const实现,不同的是继承了type_is,同时传递类型参数,也就是说可以直接指定结果类型是哪个,并且省掉了写using type = xxx,是不是方便了很多。

决策标记

假设你要设计一个IF/IF_t的元函数,如果满足条件则返回一个类型,不满足返回另外一个类型,这整个发生在编译期。如何做到呢?可以先尝试自己写写,首先要有一个主模板,还有一个特化模板;

template<bool, class T, class F>
struct IF : type_is<T> {};template<class T, class F>
struct IF<false, T, F> : type_is<F> {};

实现真的够简单优美,假设主模板的bool值是true,这样结果就是T类型,如果特化bool的值为false时,就返回F类型。也就是C++11的conditional。C++14后增加了xxxx_t表示xxxx::type。也就是说conditional_t表示conditional::type,更加方便。
那么再次想象你可能需要这样一种场景,如果传递给你的bool值是true,就返回指定类型,否则什么也不返回(或者说什么也不做)

template<bool, class T>
struct enable_if : type_is<T> {};template<class T>
struct enable_if<false, T> {};

借助上边的一个一个例子,很容易就实现了。主模板默认bool值是true,就返回T类型。特化模板是false,那么就什么也不做。
enable_if是不是很熟悉,没错的,就是标准库中的enable_if。那么这样的一个元函数,他有什么用呢?
当调用到enable_if<false,...>::type时是个错误吗?这里就可以使用在SFINAE了。通过一些重载机制,使得enable_if做到替换失败不是错误,进行重载决议选择最合适的函数执行。
我的之前文章也讲过的SFINAE了,不过brown花了一些时间对于此讲解,我们也来转述一下。
SFINAE应用于模板的隐式实例化过程中,在模板的实例化过程中分几步进行:

  1. 计算出模板参数
    • 优先使用模板函数调用时显示指定的参数
    • 否则从函数参数推断
    • 否则从默认模板参数使用
  2. 使用相应的参数替换模板参数

如果这些步骤可以产出格式正确的的代码,则实例化成功。
如果产生的代码格式是不正确的,也就是替换失败,则该函数就会从候选集里丢弃。

SFINAE使用:

template<class T>
enable_if<is_integral<T>::value, maxint_t>
f(T val) {}template<class T>
enable_if<is_floating_point<T>::value, long double>
f(T val) {}

enable_if应用在函数的返回值上,如果调用f传递是int则调用第一个函数,传递float则调用第二个。但是不论是传递int或者float都会有一个函数会实例化失败,但是这并不是错误,仅仅是从候选集中移除从而选择正确的一个。
某种程度上来讲enable_if和concept类似,concept有着坚实的数学基础。brown也仅仅是一个尝鲜,目前concept在C++20引入。

C++11标准库元函数惯例2

当一个元函数返回一个值时(以值为结果),使用value为其命名,大部分使用static constexpr修饰。一个典型值元函数的例子是:

template<class T, T v>
struct integral_constant {static constexpr T value = v;constexpr operator T() const noexcept {return value;}constexpr T operator()() const noexcept {return value;}
};

是一个常数元函数,仅仅是对其入参的值进行了封装并作为结果,这个和type_is类似的地方是,他也可以作为其他值元函数的父类,免去了重新对value进行编码及相关函数的编写。

再次看下rank

template<class T>
struct rank : integral_constant<size_t, 0> {};template<class U, size_t N>
struct rank<U[N]> : integral_constant<size_t, 1u + rank<U>::value> {};

现在有了integral_constant,就可以很方便的对rank进行封装,写法及思路基本上没有变化,仅仅是将vale和类型传给integral_constant来管理,那也就是说我的最终值结果传递integral_constant就做好了一切。

使用integral_constant的一些便利

template<bool b>
using bool_constant = integral_constant<bool, b>;using true_type = bool_constant<true>;
using false_type = bool_constant<false>;

bool_constant就是类型为bool的常数,然后借此有可以封装了true_type和false_type,也就是说true_type或者false_type就是值结果为true和false的元函数。

is_void<T>::value
bool(is_void<T>{})
is_void<T>{}();
is_void_v<T> // c++14

这是几种获取到value结果的调用方式,is_void返回一个value,可以使用::value调用,或者实例化对象获取,或者调用()操作符调用,这些在integral_constant都有提供。xxx_v的形式在c++14中提供,和xxx_t类似,也即对xxx::value的封装。

使用模板特化及继承

这里继续使用继承及特化一起的形式来展示给大家一些例子:

is_void

假设要实现一个判断类型是不是void的元函数呢?

template<class T> struct is_void : false_type {};template<> struct is_void<void> : true_type {};
template<> struct is_void<void const> : true_type {};
...

这里假设主模板中传递的类型不是void,所以让他来继承false_type,也就是说这里他的返回值是false。然后就是模板的特化了,这里只是特化了两个形式,一个是void,一个是void const,这两种类型的参数都会被判断成返回值是true。这里也是我们第一次继承true_type和false_type,当需要设定返回值是bool值时,便可以参照如此来继承。

is_same

假设要实现一个判断传递的两个类型参数是否相等的元函数呢?这里大家可以设想一下自己的实现来和下边实现比对:

template<class T, class U> struct is_same : false_type {};
template<class T> struct is_same<T, T> : true_type {};

主模板时传递两个类型的模板参数,默认是false的,然后看特化版本,仅仅是特化出来两个一样的类型的元函数的版本为true,简单且完美。到这里大家可以想象是不是自己也可以写出类似的函数了,虽然和我们平时写的代码不一样,但是原理很像,假设我们写if T == U 则为true,否则则为false,抽象成元函数时,我们只能特化相等的情况,因为这种情况是已知的。

使用is_same实现is_void

那么我们如果使用is_same再次实现is_void呢?

template<class T>
using is_void = is_same<remove_cv_t<T>, void>;// remove_cv_t
template<class T>
using remove_cv = remove_volatile<remove_const_t<T>>;template<class T>
using remove_cv_t = typename remove_cv<T>::type ;

实现也比较简单,就是将T与void比较,这里就是多了将类型T的const及volatile修饰去掉再此比较,下边也有remove_cv的实现,前边也讲过,比较容易理解。

元函数中使用参数包

从这里开始我们将模板参数是参数包的情况下的一些例子。

is_one_of

首先是判断一坨类型参数包中是否包含某个类型参数:

template<class T, class... P0toN>
struct is_one_of;template<class T>
struct is_one_of<T> : false_type {};template<class T, class... P1toN>
struct is_one_of<T, T, P1toN...> : true_type {};template<class T, class P0, class... P1toN>
struct is_one_of<T, P0, P1toN...> : is_one_of<T, P1toN...> {};

这个实现稍微有点复杂,但是也是很巧妙。首先仅仅是对is_one_of声明,不做实现,然后第一个特化版本是就一个T类型,也就是说参数包是空的,那当然是返回false了。第二个特化版本是要找类型刚好在参数包的第一个,那自然返回true。第三个版本是表示不在第一个情况下,那就先把第一个分离出来(P0),让T和其他的剩下的类型比较,也即继承is_one_of<T, P1toN...>,这样递归继承就始终判断是不是在第一个就可以了,如果找了半天没有就回到第一个特化版本了。

使用is_one_of实现is_void

template<class T>
using is_void = is_one_of<T,void,void const,void volatile,void const volatile>;

就比较简单,判断下传递过来的T是不是其中的任何一个,完美。

不做求值的操作符

brown谈到一些未求值的操作符,包含sizeof,typeid,decltype,noexcept,也就是说这些操作符要操作的表达式时,是不对表达式求值的。举例说明:decltype(foo()) 中的表达式是foo的函数调用,其实不会真的去调用这个函数,仅仅是对这个函数的签名等做检查,那么这里就是检查这个函数的返回值类型。
那么由于此特性,结合std::declval()可以一起使用。

decltype((std::declval<int>())); // 

**std::declval()**是一个函数模板,他仅仅只有声明,没有实现,你唯一可以使用他的地方是在没有定义的未求值的上下文中。作用是看起来像是一个函数调用,但是不去调用,且返回值为T的右值引用。如果想要返回值是左指的话,传递参数为T&即可。

is_copy_assignable

先来看一个应用,判断某个类型的是否可以拷贝,也即等号操作符是不是可以调用:

template<class T>
struct is_copy_assignable {
private:template<class U, class = decltype(declval<U&>() = declval<U const&>())>static true_type try_assignment(U&&);static false_type try_assignment(...);public:using type = decltype(try_assignment(declval<T>()));
};

看起来有点复杂,我们一步一步解析,首先可以看到有两个重载函数try_assignment,第一个是返回值是true_type,第二个是false_type。那也就是说第一个是表示可以对拷贝操作符调用。他是如何知道的呢,
decltype(declval<U&>() = declval<U const&>())这句是关键,这里就是去获取等号操作符函数的返回值,使用declval来调用等号操作符,但是仅仅是声明没有真正调用,这里是将一个U const&的对象赋值给U&的形式,也即等号的签名形式。第二个重载版本就很简单,参数就是三个点,表示最坏的匹配,其他重载函数找不到就找它。
最后看is_copy_assignable的type,也即对外的返回类型,其实就是检查try_assignment的返回值而已,这样如果可以对等号调用就返回true_type,否则就是false_type。

上边是C++11之后的实现,brown这里也提到了C++11之前对此的实现,思路几乎一致,不够有点丑陋。还无法使用true_type和false_type时,使用typedef char (&yes)[1]typedef char (&no)[2]来代替,及使用char[1]数组表示yes,char[2]数组表示no,这里也就是返回的类型,使用sizeof这个返回值类型来区别。即sizeof(try_assignment(...)),不过这里他没说到declval的替代方案是什么。

void_t

接下来就是大名鼎鼎的void_t了,brown花了很大的篇幅来讲述这个,只能用精美绝伦的来形容,当时在场的听众也是极为赞赏。

template<class...>
using void_t = void; 

这就是实现,及其简单优雅,表述的意义是接收多个参数,表示的类型就是void。也就是无论你传给我啥类型,我就给你返回一个确定的类型。
那大家可能会想,这会有啥用呀,我自己都可以实现,继续往下看。
假设需要实现一个检查一个类里是否有某个type的成员:

template<class, class = void>
struct has_type_member : false_type{};template<class T>
struct has_type_member<T, void_t<typename T::type>> : true_type{};// call
has_type_member<T>::value

主模板默认是没有的,没什么好说。关键在与特化版本这里,使用void_t包装里T::type,重点是T::type是否可以正常调用,如果是则就是返回true_type,否则就会回到第一个主模板那里了(SFINAE)。
那也就是说void_t反而成了会检查传递给他的类型是不是合法的功能了,如果合法那就返回void,不然就行不通。666

再次实现is_copy_assignable

有了void_t了,brown再次实现is_copy_assignable,来看:

template<class T>
using copy_assignment_t = decltype(declval<T&>() = declval<T const&>());template<class T, class = void>
struct is_copy_assignable : false_type {};template<class T>
struct is_copy_assignable<T, void_t<copy_assignment_t<T>>>: is_same<copy_assignment_t<T>, T&> {};

首先需要一个表达格式让void_t去检查,这里是单独拿出来的copy_assignment_t,实现我们上边见过,就是对等号操作符的调用声明,切copy_assignment_t还会获取到等号操作符的返回值。
然后开始is_copy_assignable主模板是默认false,特化模板同上边实现基本一致,就是void_t中会去检查copy_assignment_t是否合法,这里合法后还会再次校验返回值是不是T&,如此便是true_type,否则就会到主模板那里false_type。

相信到这里大家也明白了void_t的用法,也就是它可以做到去校验传递给他的调用得出类型或者表达式等等是否是合法的。如果需要的是is_move_assignable,仅仅把copy_assignment_t那里的T const&换成T&&。

演讲到这里,有一个clang标准库维护的嘉宾说他也需要一个这样的东西,实现了一个is_well_form,但是他的实现相比较起来有限的多,回家要把它撕了。哈哈哈。

技巧及工具总结

这里就是做了一下总结,brown讲到前边说了类型的元函数,使用static const/constexpr的值元函数,及元函数的使用继承特化等等技术,以及SFINAE,未求值操作符,参数包的元函数以及void_t等等。
brown最后用一段话结束演讲,我把它贴到这里:

Although we're professionals now, 
we all started out as humble students - .... 
Back then, everything was new, and we had no real way of knowing 
whether what we were looking at was wizardry or WTF

到此就结束了,希望我的搬运能给大家带来知识,腰酸背痛,点个赞吧

ref

  • https://www.youtube.com/watch?v=Am2is2QCvxY&list=PLHTh1InhhwT7esTl1bRitiizeEnksGU7J&index=64
  • https://www.bilibili.com/video/BV1JK4y1D7Yz/?spm_id_from=autoNext&vd_source=1f66d9ea5af0d60321b7aa0c6bd28d66

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/197943.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

游戏mod制作--引擎与解包

摘要 游戏mod的制作过程第一步就是需要将原始的游戏工程文件进行解包&#xff0c;得到相应的资源文件&#xff08;贴图&#xff0c;音频&#xff0c;事件&#xff0c;模型甚至源代码等&#xff09;&#xff0c;这个时候下一步就是需要将解包出来的文件进行分类索引&#xff0c…

服务器内存使用率高的原因及解决方法_Maizyun

服务器内存使用率高的原因及解决方法 在服务器运行过程中&#xff0c;内存使用率过高可能会引发一系列问题&#xff0c;如性能下降、应用程序崩溃等。本文将深入探讨服务器内存使用率高的原因&#xff0c;并提出相应的解决方法。 一、内存使用率高的原因 应用程序缺陷&#…

20:kotlin 类和对象 --泛型(Generics)

类可以有类型参数 class Box<T>(t: T) {var value t }要创建类实例&#xff0c;需提供类型参数 val box: Box<Int> Box<Int>(1)如果类型可以被推断出来&#xff0c;可以省略 val box Box(1)通配符 在JAVA泛型中有通配符?、? extends E、? super E&…

25. K 个一组翻转链表

给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点内部的值…

自媒体原创改写工具,自媒体首发改写软件

自媒体平台已成为许多创作者表达观点、分享知识和积累影响力的关键渠道。创作是需要技巧和经验的。本文将分享一些自媒体文章改写技巧&#xff0c;并推荐一系列优秀的自媒体文章改写工具&#xff0c;帮助您提升创作效率&#xff0c;创作出更优秀的文章。 自媒体文章改写技巧 …

Backend - Django makemigrations

目录 一、迁移命令 &#xff08;一&#xff09;前提 &#xff08;二&#xff09;生成迁移文件 &#xff08;三&#xff09;执行迁移 二、迁移问题 1. Error&#xff1a;No changes detected 2. Error&#xff1a;You are trying to add a non-nullable field XXX to XXX…

[读论文]BK-SDM: A Lightweight, Fast, and Cheap Version of Stable Diffusion

github: GitHub - Nota-NetsPresso/BK-SDM: A Compressed Stable Diffusion for Efficient Text-to-Image Generation [ICCV23 Demo] [ICML23 Workshop] ICML 2023 Workshop on ES-FoMo 简化方式 蒸馏方式&#xff08;训练Task蒸馏outKD-FeatKD&#xff09; 训练数据集 评测指标…

在intelliJ spring boot gradle插件3.2.0中未找到匹配的变量

我正在尝试使用spring启动Gradle插件的版本3.2.0。这是我的build.gradle文件&#xff1a; plugins {id javaid org.springframework.boot version 3.2.0id io.spring.dependency-management version 1.1.4 }group com.yaxin version 0.0.1-SNAPSHOTjava {sourceCompatibilit…

GPIO的使用--时钟使能含义--代码封装

目录 一、时钟使能的含义 1.为什么要时钟使能&#xff1f; 2.什么是时钟使能&#xff1f; 3.GPIO的使能信号&#xff1f; 二、代码封装 1.封装前完整代码 2.封装结构 封装后代码 led.c led.h key.c key.h main.c 一、时钟使能的含义 1.为什么要时钟使能&#xff1f…

Python开发运维:Python 3.8 常用标准库

目录 一、理论 1.Python3.8 标准库 2.常用标准库 二、问题 1.Python 正则表达式如何实现 一、理论 1.Python3.8 标准库 &#xff08;1&#xff09;官网 Python 标准库 — Python 3.8.17 文档 &#xff08;2&#xff09;其他版本下拉列表查询 2.常用标准库 &#xff0…

MySQL笔记-第01章_数据库概述

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第01章_数据库概述1. 为什么要使用数据库2. 数据库与数据库管理系统2.1 数据库的相关概念2.2 数据库与数据库管理系统的关系2.3 常见的数据库…

JVM参数配置推荐

JVM配置建议 参数 备注/参数释义 规范 JVM GC方法 ParallelGC&#xff1a;1.8默认&#xff0c;高吞吐量&#xff0c;响应时间不敏感 CMS&#xff1a;响应优先&#xff0c;堆内存8G以下优先选择 G1&#xff1a;响应优先&#xff0c;堆内存8G及以上选择 C端应用&#xff1a;…

微积分中值定理的双存在值证明问题@寻找辅助函数证明中值问题

文章目录 abstract微积分中值定理的双存在值证明题举例综合使用积分中值定理和微分中值定理例 双中值问题构造辅助函数法综合小结 abstract 微积分中值定理的双存在值证明问题寻找辅助函数证明中值问题 微积分中值定理的双存在值证明题举例 综合使用积分中值定理和微分中值定…

Linux--网络编程-ftp(TCP)网络通信-文件交互

项目要求&#xff1a;实现以下内容 远程控制&#xff1a; 1、查看服务器当前路径文件 ls 3、进入、退出服务器文件夹 cd 4、上传文件到服务器 put xxx 本地控制&#xff1a; 1、查看本地&#xff08;客户端&#xff09;文件 lls 2、进入客户端文件夹 lcd 3、获取服务器的文件…

音频录制软件哪个好?帮助你找到最合适的一款

音频录制软件是日常工作、学习和创作中不可或缺的一部分。选择一个适合自己需求的录音软件对于确保音频质量和提高工作效率至关重要。可是您知道音频录制软件哪个好吗&#xff1f;本文将深入探讨两种常见的音频录制软件&#xff0c;通过详细的步骤指南&#xff0c;帮助您了解它…

编写Java应用程序,输出满足1+2+3+……+n<8888的最大正整数n。

源代码&#xff1a; public class Main { public static void main(String[] args) { int i 1; int sum 0; for(i 1;;i){ sum i; if (sum >8888) break; } System.out.println(i-1); } } 实验运行截图&#xff1a;

数组实现循环队列(新增一个空间)

目录 一、前言 1.如何实现循环&#xff1f; 2.如何判断队列为空&#xff1f; 3.如何判断队列为满&#xff1f; 二、循环队列的结构定义 三、循环队列的创建及其初始化 四、入队 五、出队 六、取队头元素 七、取队尾元素 八、判空 九、判满 十、销毁队列 一、前言 …

【滑动窗口】LeetCode2953:统计完全子字符串

作者推荐 [二分查找]LeetCode2040:两个有序数组的第 K 小乘积 本题其它解法 【离散差分】LeetCode2953:统计完全子字符串 题目 给你一个字符串 word 和一个整数 k 。 如果 word 的一个子字符串 s 满足以下条件&#xff0c;我们称它是 完全字符串&#xff1a; s 中每个字符…

深入理解:指针变量的解引用 与 加法运算

前言 指针变量的解引用和加法运算是非常高频的考点&#xff0c;也是难点&#xff0c;因为对初学者的不友好&#xff0c;这就导致了各大考试都很喜欢在这里出题&#xff0c;通常会伴随着强制类型转换、二维数组、数组指针等一起考查大家对指针的理解。但是不要怕&#xff0c;也许…

论文解读--PointPillars- Fast Encoders for Object Detection from Point Clouds

PointPillars--点云目标检测的快速编码器 摘要 点云中的物体检测是许多机器人应用(如自动驾驶)的重要方面。在本文中&#xff0c;我们考虑将点云编码为适合下游检测流程的格式的问题。最近的文献提出了两种编码器;固定编码器往往很快&#xff0c;但牺牲了准确性&#xff0c;而…