C++20 Concepts实现可变参数累加函数的问题排查与修正
可变参数模板函数Accum的实现问题分析
需求说明
实现名为Accum的可变参数模板函数,需满足:
- 累加参数包中支持
init_type{} + value算术加法的类型的值 - 若参数包为空,返回
Init类型的默认构造值 - 示例要求:
Accum<int>(10,20,1.1,"string")应返回31.1
初始实现思路
- 思路1:基于Concept的通用方法
通过AddableC概念约束AccumValue重载,符合约束的返回原值,不符合的返回Init{};但传入字符串时编译失败。 - 思路2:针对特定类型的重载方法
为int、double等需要累加的类型重载Value函数,非目标类型返回Init{},可正常运行。
核心问题
- 思路1失效的原因是什么?
- 有没有可行的修复方案?
- 初始实现在概念或语法上遗漏了什么?
- 正确的实现思路应该是什么?
测试环境:Microsoft Visual Studio Community 2022 Version 17.3.5
失效原因
原来的AddableC概念只检查Init{} + t是否合法,但字符串字面量是const char*类型,C++允许整数和指针做加法(指针算术),所以AddableC<int, const char*>会被判定为满足约束。这导致AccumValue返回字符串指针,后续累加时会把指针地址和整数相加,要么得到错误结果,要么因类型不匹配触发编译失败——完全违背了我们要做算术累加的初衷。
修复方案与语法遗漏
用户更新后的代码通过添加not std::is_pointer_v<std::decay_t<T>>约束,排除了所有指针类型,彻底解决了字符串字面量被误判的问题。
初始实现的遗漏点:
- 未区分算术加法与指针加法:概念只验证表达式合法性,没过滤掉指针这种不符合业务需求的类型
- 未正确使用转发引用:原来的
AccumValue用T const&接收参数,无法完美转发右值,可能导致不必要的拷贝
正确实现思路
- 完善Concept约束:在验证
Init{} + t合法的基础上,排除指针类型(或进一步限定为算术类型),确保只保留支持算术加法的类型 - 使用完美转发:用
T&&转发引用配合std::forward,保留参数的原始值类别,提升传递效率 - 利用折叠表达式:
(Init{} + ... + AccumValue<Init>(std::forward<Args>(args)))天然支持空参数包返回Init{}的需求,无需额外处理
修复后的完整可运行代码
import <iostream>; #include <type_traits> // 修正后的Concept:验证加法合法性+排除指针类型 template <typename Init, typename T> concept AddableC = requires (T&& t) { Init{} + t; } && !std::is_pointer_v<std::decay_t<T>>; template <typename Init, typename T> Init AccumValue(T&&) { return Init{}; } template <typename Init, typename T> requires AddableC<Init, T> auto AccumValue(T&& value) { return std::forward<T>(value); } template<typename Init, typename ... Args> auto Accum(Args&& ... args) { return (Init{} + ... + AccumValue<Init>(std::forward<Args>(args))); } // 思路2的实现(保留对比) template<typename Init, typename T> auto Value(T const&) { return Init{}; } template<typename Init> auto Value(int const& i) { return i; } template<typename Init> auto Value(double const& d) { return d; } template<typename Init, typename ... Args> auto Sum(Args&& ... args) { return (Init{} + ... + Value<Init>(std::forward<Args>(args))); } int main() { auto result1{ Accum<int>(10, 20, 1.1) }; // 输出31 std::cout << result1 << std::endl; auto result2{ Accum<int>(20, 40, 2.2, "string")}; // 现在可正常编译,输出62 std::cout << result2 << std::endl; auto result3{ Sum<double>(1, 2, 3.3, "asda") + Sum<double>()}; // 输出6.3 std::cout << result3 << std::endl; }
内容的提问来源于stack exchange,提问作者Soup Endless
相关产品推荐
相关产品推荐

