如何正确声明适配二元运算的C++ concept及相关问题咨询
问题1:如何修复二元运算concept的声明逻辑
你之前的两种实现错误原因如下:
- 第一种方案直接比较
Op和Ret(Arg1, Arg2)函数类型的相等性,但是可调用对象的类型远不止原生函数,还包括lambda、仿函数、std::function包装器等,这些类型和原生函数类型并不相同,因此匹配失败。 - 第二种方案通过
std::function包装后比较类型相等性同样错误,std::function可以兼容存储所有签名匹配的可调用对象,但不同来源的可调用对象包装后的std::function类型本身是相同的,且直接比较类型的逻辑无法覆盖隐式类型转换的场景,因此校验失败。
正确的实现应该使用C++标准库提供的std::invocable系列工具,根据你的需求有两种可选方案:
- 仅校验可调用性,不限制返回值类型:
// 注意:自定义concept必须放在自己的命名空间,不要放到std中 namespace utils { template <typename Op, typename Arg1, typename Arg2> concept binary_op = std::is_invocable_v<Op, Arg1, Arg2>; }
- 同时校验调用返回值可以转换为指定类型,更适合你的快速幂场景(保证运算结果可以赋值给
acc变量):
namespace utils { template <typename Op, typename Ret, typename Arg1, typename Arg2> concept binary_op_returnable = std::is_invocable_r_v<Ret, Op, Arg1, Arg2>; }
对应的power函数可以改写为:
template <std::integral I, utils::binary_op_returnable<I, I, I> Op> I power(I acc, I a, I n, Op op) { while (n-- != I{0}) { acc = op(acc, a); } return acc; }
注意:不要向std命名空间添加自定义的非特化内容,这属于C++标准规定的未定义行为,你的补充版本将binary_op放到std命名空间是错误的。
问题2:为什么C++标准库没有内置固定元数的函数concept
主要有三个原因:
- 标准库已经提供了更通用的
std::invocable、std::regular_invocable等concept(C++20起引入),完全可以满足固定元数的校验需求:校验一元函数就传入Op, T两个模板参数,校验二元函数就传入Op, T1, T2三个模板参数,无需单独封装固定元数的concept。 - 固定元数的函数定义过于模糊,很多可调用对象支持多重重载,既可以接受1个参数也可以接受多个参数,单独的“一元函数”“二元函数”concept无法覆盖这类场景,灵活性不足。
- 标准库的设计原则是优先提供通用基础组件,特定语义的封装交给用户根据业务场景自定义,避免过度设计导致的适用范围受限。
额外优化建议
如果你的快速幂后续要扩展到更通用的场景(比如运算对象不是整数),可以去掉std::integral的限制,或者将运算对象和计数用的整数类型分开定义,更符合《Elements of Programming》中的通用设计思路。
内容的提问来源于stack exchange,提问作者hannibal
相关产品推荐
相关产品推荐

