为类构造器添加SFINAE以仅接受Boost.Geometry点类型
问题描述
Boost.Geometry针对平面笛卡尔坐标系提供了两种点类型,可指定浮点类型(如double、float):
(假设代码中使用namespace bg = boost::geometry;)
其中FPT为指定的浮点类型:
bg::model::point<FPT, 2, bg::cs::cartesian> pt1; bg::model::d2::point_xy<FPT> pt2;
备注:第二种类型继承自第一种类型
现在需要为自定义类编写仅接受Boost点作为单参数的构造器,由于类中存在其他单参数构造器,必须通过SFINAE约束让该构造器只匹配上述两种点类型。
现有代码框架如下:
template<typename FPT> struct Point { FPT x,y; template<typename BPT> // 目标:仅匹配Boost Point Type Point( const BPT& in ) { /* 这里需要实现Boost点的构造逻辑 */ } template<typename T> // 其他类型的构造器 Point( const T& in ) { /* 其他逻辑 */ } };
根据Boost.Geometry文档,Boost点类需遵循Point概念:
必须存在
traits::tag的特化,将point_tag定义为类型
尝试了以下实现,但编译失败,提示template argument deduction/substitution failed:
template<typename FPT> // Floating Point Type struct Point { FPT x,y; template< typename BPT, // Boost Point Type typename std::enable_if< std::is_same< bg::traits::tag<BPT>, bg::point_tag >::value, BPT >::type* = nullptr > Point( const BPT& pt ): x{ bg::get<0>(pt) }, // 可按需添加static_cast y{ bg::get<1>(pt) } {} }; int main() { bg::model::point<double, 2, bg::cs::cartesian> pt1; bg::model::d2::point_xy<double> pt2; Point<float> p1(pt1); Point<double> p2(pt2); }
请问该如何解决这个编译错误?
解决方案
编译失败的核心原因是:bg::traits::tag<BPT>在模板参数推导阶段无法直接正确解析——Boost.Geometry的traits特化依赖参数依赖查找(ADL),且对于继承自bg::model::point的类型(比如point_xy),直接使用bg::traits::tag<BPT>无法匹配到对应的特化。
以下是两种可行的修复方案:
方案一:使用Boost内置的点类型检查工具
Boost.Geometry提供了bg::is_point trait,可直接用于SFINAE约束,简洁且可靠:
#include <boost/geometry.hpp> #include <boost/geometry/core/point.hpp> #include <type_traits> namespace bg = boost::geometry; template<typename FPT> struct Point { FPT x,y; // 仅匹配Boost Point类型的构造器 template<typename BPT, typename = std::enable_if_t<bg::is_point<BPT>::value>> Point(const BPT& pt) : x(static_cast<FPT>(bg::get<0>(pt))), y(static_cast<FPT>(bg::get<1>(pt))) {} // 其他单参数构造器:添加约束排除Boost点,避免二义性 template<typename T, typename = std::enable_if_t<!bg::is_point<T>::value>> Point(const T& in) { // 其他类型的构造逻辑 } }; int main() { bg::model::point<double, 2, bg::cs::cartesian> pt1; bg::model::d2::point_xy<double> pt2; Point<float> p1(pt1); Point<double> p2(pt2); }
方案二:修复原有的traits检查逻辑
如果不想依赖bg::is_point,可调整SFINAE条件,确保bg::traits::tag能正确解析:
#include <boost/geometry.hpp> #include <type_traits> namespace bg = boost::geometry; template<typename FPT> struct Point { FPT x,y; template<typename BPT, // 先对BPT做衰减处理,再获取tag typename Tag = typename bg::traits::tag<typename std::decay_t<BPT>>::type, typename = std::enable_if_t<std::is_same_v<Tag, bg::point_tag>>> Point(const BPT& pt) : x(static_cast<FPT>(bg::get<0>(pt))), y(static_cast<FPT>(bg::get<1>(pt))) {} // 其他构造器:同样排除Boost点类型 template<typename T, typename Tag = typename bg::traits::tag<typename std::decay_t<T>>::type, typename = std::enable_if_t<!std::is_same_v<Tag, bg::point_tag>>> Point(const T& in) { // 其他类型的构造逻辑 } }; int main() { bg::model::point<double, 2, bg::cs::cartesian> pt1; bg::model::d2::point_xy<double> pt2; Point<float> p1(pt1); Point<double> p2(pt2); }
关键注意事项
- 构造器互斥:必须确保Boost点构造器和其他单参数构造器的约束互斥,避免编译时二义性。
- 类型安全转换:添加
static_cast<FPT>确保坐标类型匹配,避免隐式转换带来的精度问题或编译警告。 - 头文件依赖:确保包含
boost/geometry/core/point.hpp等必要头文件,保证Boost的traits系统和ADL正常工作。
内容的提问来源于stack exchange,提问作者kebs
相关产品推荐
相关产品推荐

