vue 判断函数是否存在_如何判断模板类中是否存在函数f ?
一个典型的stackoverflow风格的问题作为文章标题,以此避免挖空心思想名字的纠结。问题描述回到这个问题,常写模板的同学应该能心领神会需求的来源。举个简单例子,在三维数据处理中,经常会和空间点point打交道,point中会有很多种信息需要存储,比如空间坐标 x/y/z,比如法向信息 nx/ny/nz,比如颜色信息 R/G/B等,这些信息并非都是必须,在不同应用中会有不同的取舍。为了简单起见
一个典型的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的宝藏刚刚打开,还有非常多值得挖掘的地方。
参考文献
- omni blogs
- https://en.cppreference.com/w/cpp/language/constraints
更多推荐
所有评论(0)