网站首页 > 博客文章 正文
一个典型的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:简单、单类型参数概念使用缩略记法更好
你 发表评论:
欢迎- 07-08Google Cloud Platform 加入支持 Docker 的容器引擎
- 07-08日本KDDI与Google Cloud 签署合作备忘录,共探AI未来
- 07-08美国Infoblox与Google Cloud合作推出云原生网络和安全解决方案
- 07-08GoogleCloud为Spanner数据库引入HDD层,将冷存储成本降低80%
- 07-08谷歌推出Cloud Dataproc,缩短集群启动时间
- 07-08Infovista与Google Cloud携手推进射频网络规划革新
- 07-08比利时Odoo与Google Cloud建立增强合作,扩大全球影响力
- 07-08BT 和 Google Cloud 通过 Global Fabric 加速 AI 网络
- 最近发表
-
- Google Cloud Platform 加入支持 Docker 的容器引擎
- 日本KDDI与Google Cloud 签署合作备忘录,共探AI未来
- 美国Infoblox与Google Cloud合作推出云原生网络和安全解决方案
- GoogleCloud为Spanner数据库引入HDD层,将冷存储成本降低80%
- 谷歌推出Cloud Dataproc,缩短集群启动时间
- Infovista与Google Cloud携手推进射频网络规划革新
- 比利时Odoo与Google Cloud建立增强合作,扩大全球影响力
- BT 和 Google Cloud 通过 Global Fabric 加速 AI 网络
- NCSA和Google Cloud合作开发AI驱动的网络防御系统,加强泰国网络空间的安全性
- SAP将在沙特阿拉伯 Google Cloud 上推出BTP服务
- 标签列表
-
- ifneq (61)
- 字符串长度在线 (61)
- googlecloud (64)
- messagesource (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)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)