条件编译运算符模板为何会改变另一运算符的可用性?
跟进提问:自动生成缺失的标量-向量乘法运算符
嘿,我接着之前那个《统一检查成员函数、自由函数及运算符是否存在》的问题继续问哈——我现在想实现这么个功能:只有当目标向量类没提供scalar * vector和vector * scalar这两种乘法运算符时,自动生成对应的运算符。我试着写了个检测模板来判断运算符是否存在,但遇到了问题,代码片段如下:
template<typename C, typename Ret, typename Arg> struct has_operator_mult { private: template<typename L, typename R, std::enable_if_t< std::is_convertible_v<decltype(std::declval<L>() * std::declval<R>()), Ret>, bool> = true> static std::true_type test(int); template<typename, typename> static std::false_type test(...); public: static constexpr bool value = decltype(test<C, Arg>(0))::value; };
问题分析与解决方案
首先得说,你当前的检测模板有几个容易踩坑的地方:
- 没区分成员运算符(比如
vector::operator*(scalar))和全局自由运算符(比如operator*(scalar, vector)),可能会漏判或者误判 std::is_convertible_v的限制太严格了——很多时候我们只需要乘法表达式合法就行,不需要结果必须能转成指定的Ret类型- 模板参数传递只覆盖了
vector * scalar这一种方向,没处理scalar * vector的情况
第一步:正确的运算符存在性检测
我们可以分开检测两个方向的乘法,同时兼容成员和全局运算符的场景,用C++17的std::void_t来实现最简洁的表达式SFINAE检测:
#include <type_traits> #include <utility> // 通用检测:L * R 表达式是否合法 template<typename L, typename R, typename = void> struct has_multiply : std::false_type {}; template<typename L, typename R> struct has_multiply<L, R, std::void_t<decltype(std::declval<L>() * std::declval<R>())>> : std::true_type {}; // 辅助变量模板,用起来更方便 template<typename L, typename R> constexpr bool has_multiply_v = has_multiply<L, R>::value; // 分别封装两个方向的检测 template<typename Vec, typename Scalar> constexpr bool has_scalar_vec_mult = has_multiply_v<Scalar, Vec>; template<typename Vec, typename Scalar> constexpr bool has_vec_scalar_mult = has_multiply_v<Vec, Scalar>;
如果需要兼容C++14,自己实现个极简版void_t就行:
template<typename...> using void_t = void;
第二步:仅在缺失时生成运算符
接下来用std::enable_if做条件判断,只在对应运算符不存在的时候生成全局运算符:
// 生成 scalar * vector template<typename Vec, typename Scalar> std::enable_if_t<!has_scalar_vec_mult<Vec, Scalar>, Vec> operator*(const Scalar& s, const Vec& v) { Vec result = v; // 这里假设向量支持范围遍历,你可以根据实际情况调整实现 for (auto& elem : result) { elem *= s; } return result; } // 生成 vector * scalar,直接复用上面的实现减少重复代码 template<typename Vec, typename Scalar> std::enable_if_t<!has_vec_scalar_mult<Vec, Scalar>, Vec> operator*(const Vec& v, const Scalar& s) { return s * v; }
额外优化(可选)
如果想限制Vec必须是符合预期的向量类型(比如要求有begin()/end()成员),可以在enable_if里加额外约束,比如结合C++20的概念:
template<typename T> concept VectorLike = requires(T t) { t.begin(); t.end(); typename T::value_type; }; template<VectorLike Vec, typename Scalar> std::enable_if_t<!has_scalar_vec_mult<Vec, Scalar>, Vec> operator*(const Scalar& s, const Vec& v) { // 实现同上 }
内容的提问来源于stack exchange,提问作者Devon Cornwall
相关产品推荐
相关产品推荐

