网站首页 > 博客文章 正文
一个典型的stackoverflow风格的问题作为文章标题,以此避免挖空心思想名字的纠结。
问题描述
回到这个问题,常写模板的同学应该能心领神会需求的来源。
举个简单例子,在三维数据处理中,经常会和空间点point打交道,point中会有很多种信息需要存储,比如空间坐标 x/y/z,比如法向信息 nx/ny/nz,比如颜色信息 R/G/B等,这些信息并非都是必须,在不同应用中会有不同的取舍。为了简单起见,定义两个模板类 point_xyz, point_xyz_normal。两者的差异是带法向的模板类多了取值函数 nx(),ny(),nz()。
template<typename value_type>
class point_xyz
{
public:
const value_type& x()const { return xyz_[0]; }
const value_type& y()const { return xyz_[1]; }
const value_type& z()const { return xyz_[2]; }
protected:
Eigen::Matrix<value_type, 3, 1> xyz_;
};
template <typename FloatType>
class point_xyz_normal
{
public:
const value_type& x()const { return xyz_[0]; }
const value_type& y()const { return xyz_[1]; }
const value_type& z()const { return xyz_[2]; }
const value_type& nx()const { return nxyz_[0]; }
const value_type& ny()const { return nxyz_[1]; }
const value_type& nz()const { return nxyz_[2]; }
protected:
Eigen::Matrix<value_type, 3, 1> xyz_;
Eigen::Matrix<value_type, 3, 1> nxyz_;
};
接下来我们定义另一个处理函数func,其定义如下:
template <typename PointAttribute>
void func(const PointAttribute &p)
{
if p has nx():
......
if p doesn't have nx():
......
}
当然上述代码无法通过编译,写在这只是为了表示,func函数中希望针对p的不同性质做不同的处理。
有同学可能会问,为什么不用模板偏特化,针对point_xyz,和point_xyz_normal做特化版本?原因主要有两点:
- 做不同的特化版本会导致函数体内的实现出现冗余,在if判断前后会有大量相同的代码存在于不同的特化版本中,导致维护成本高;
- 模板偏特化是针对具体类型的特化,而在此处,我们并不在意模板参数PointAttribute是什么类型,只需要判断PointAttribute中是否存在函数nx。
解决方法
怎么办呢?找遍网络,发现得依靠C++20的Concepts和Requires来实现。先上答案:
template <typename PointAttribute>
concept has_normal = requires(PointAttribute t)
{
t.nx();
t.ny();
t.nz();
};
template <typename PointAttribute>
void func(const PointAttribute &p)
{
if constexpr (has_normal<PointCloud::point_attribute>){
...
}else{
...
}
}
以上代码需要编译器支持C++20特性才能编译。msvc 2019并不支持完整的C++20特性,而且需要开启/std:c++latest才能使用。如果你使用的是CMake,需要用下列语句来启用,否则msvc默认的__cplusplus版本号还停留在1997呢。
target_compile_options(${target_name} PRIVATE "/Zc:__cplusplus")
target_compile_options(${target_name} PRIVATE "/std:c++latest")
Concept和Requires的详细概念要展开来讨论得再开个系列了,我也是刚开始接触,理解不到位的地方,请大家斧正。
解释与讨论
Omni Blogs 做了个非常好的介绍。我借用其中的例子来做讨论。
简单来讲,Concept和SFINAE的行为有点像,都可以对模板做出一定的约束。我们看下例,希望对不同类型的模板参数T调用不同的log处理函数。注意,此处无法针对具体类型做偏特化,因为要判断T是整形或浮点型,而不是判断T是int还是double,需要注意其中的差别。
template <typename T>
void log(T&& x)
{
log_integral(x);
}
template <typename T>
void log(T&& x)
{
log_floating_point(x);
}
如果使用SFINAE,其实现如下:
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
void log(T&& x)
{ /* implementation irrelevant */ }
template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
void log(T&& x)
{ /* implementation irrelevant */ }
或者可以借用constexpr让代码变短一点:
template <typename T>>
void log(T&& x)
{
if constexpr (std::is_integral_v<T>)
{
}else if constexpr (std::is_floating_point_v<T>)
{
}
}
而使用Concept可以让代码含义更清晰:
template <typename T>
requires std::integral<T>
void log(T&& x)
{ ... }
template <typename T>
requires std::floating_point<T>
void log(T&& x)
{ ... }
其中requires表示模板参数T需要满足特定的条件,而这里的条件分别是std::integral<T>和std::floating_point<T>。
Concept的写法有许多种,在此不赘述,感兴趣的可以看Omni Blog。回到本文的问题,我们定义了一个concept,要求t中具备nx等函数,而这个concept可以用 if constexpr在编译期判断。
template <typename PointAttribute>
concept has_normal = requires(PointAttribute t)
{
t.nx();
t.ny();
t.nz();
};
至此,问题解决,但concept的宝藏刚刚打开,还有非常多值得挖掘的地方。
参考文献
- https://omnigoat.github.io/2020/01/19/cpp20-concepts/
- https://en.cppreference.com/w/cpp/language/constraints
猜你喜欢
- 2024-10-12 C++核心准则T.24:用标签类或特征区分只有语义不同的概念
- 2024-10-12 用苹果发布会方式打开C++20(苹果在哪开发布会)
- 2024-10-12 C++核心准则T.25:避免互补性约束(规矩是一种约束,一种准则)
- 2024-10-12 C++核心准则T.21:为概念定义一套完整的操作
- 2024-10-12 C++核心准则T.5:结合使用泛型和面向对象技术应该增强效果
- 2024-10-12 C++经典书籍(c++相关书籍)
- 2024-10-12 C++一行代码实现任意系统函数Hook
- 2024-10-12 C++核心准则T.11:只要可能就使用标准概念
- 2024-10-12 C++核心准则T.48:如果不能用概念,用enable_if
- 2024-10-12 C++核心准则T.13:简单、单类型参数概念使用缩略记法更好
你 发表评论:
欢迎- 最近发表
-
- 给3D Slicer添加Python第三方插件库
- Python自动化——pytest常用插件详解
- Pycharm下安装MicroPython Tools插件(ESP32开发板)
- IntelliJ IDEA 2025.1.3 发布(idea 2020)
- IDEA+Continue插件+DeepSeek:开发者效率飙升的「三体组合」!
- Cursor:提升Python开发效率的必备IDE及插件安装指南
- 日本旅行时想借厕所、买香烟怎么办?便利商店里能解决大问题!
- 11天!日本史上最长黄金周来了!旅游万金句总结!
- 北川景子&DAIGO缘定1.11 召开记者会宣布结婚
- PIKO‘PPAP’ 洗脑歌登上美国告示牌
- 标签列表
-
- ifneq (61)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)