如何用C++20 source_location替代宏,实现调用点行号显示并保持简洁API
用C++20 source_location替代宏实现带调用点行号的setVals
核心问题分析
你当前用lambda封装的方式存在本质问题:source_location::current()在lambda内部调用时,会返回lambda定义处的行号,而非setVals调用处的行号,这和宏的预期行为完全不符。要解决这个问题,必须让source_location的获取逻辑直接绑定到setVals的调用点,而非中间封装的lambda。
可行解决方案:利用函数默认参数注入source_location
C++20允许将std::source_location作为函数的默认参数,此时source_location::current()会自动捕获调用点的位置信息。我们可以调整模板函数的设计,把source_location作为默认参数传入,同时保留setVals(a,b)的简洁调用形式。
调整后的实现代码
#include <iostream> #include <type_traits> #include <source_location> #include <cassert> #include <concepts> namespace myLib { // 非法调用处理:参数类型不匹配或无有效参数 template<typename EnumT, typename... Ts> constexpr void setValsImpl(Ts const&..., const std::source_location loc = std::source_location::current()) { std::cout << "line #" << loc.line() << ": illegal call to setVals()\n"; static_assert(std::is_enum_v<EnumT>, "EnumT must be an enum type"); } // 合法调用处理:所有参数都是目标枚举类型的引用 template<typename EnumT> constexpr void setValsImpl(EnumT&... vals, const std::source_location loc = std::source_location::current()) requires (sizeof...(vals) > 0) { std::cout << "line #" << loc.line() << ": legal call to setVals()\n"; // 此处可添加实际的赋值逻辑 } } // 绑定具体枚举类型的简洁封装宏 #define SET_VALS_FOR_ENUM(EnumType) \ constexpr auto setVals = [](auto&&... args) { \ myLib::setValsImpl<EnumType>(std::forward<decltype(args)>(args)...); \ } int main() { enum enumVals : short { a = 0, b, c, count }; SET_VALS_FOR_ENUM(enumVals); // 绑定当前枚举类型 setVals(a, b); // 合法调用,输出调用处行号 setVals(b, "text"); // 非法调用,输出调用处行号 }
方案说明
- source_location作为默认参数:将
std::source_location设为函数的默认参数,调用时编译器会自动注入当前调用点的位置,完美替代宏中的__LINE__。 - 模板重载与约束:通过模板重载和
requires约束区分合法/非法调用,合法版本要求所有参数都是目标枚举类型的引用,非法版本处理类型不匹配或无参数的情况。 - 简洁API保留:通过宏
SET_VALS_FOR_ENUM绑定具体枚举类型,生成的setVals仍支持setVals(a,b)的简洁调用形式。 - 友好错误提示:非法调用时会输出调用点行号和自定义提示,比编译器默认错误信息更直观;同时用
static_assert在编译期检查枚举类型的合法性。
替代思路:直接定义模板函数(无需lambda)
如果不想用宏绑定枚举类型,也可以直接定义模板函数,让用户调用时显式指定枚举类型,仅牺牲少量简洁性:
namespace myLib { template<typename EnumT, typename... Ts> constexpr void setVals(Ts&&... args, const std::source_location loc = std::source_location::current()) { if constexpr ((std::is_same_v<std::decay_t<Ts>, EnumT> && ...)) { std::cout << "line #" << loc.line() << ": legal call to setVals()\n"; } else { std::cout << "line #" << loc.line() << ": illegal call to setVals()\n"; static_assert(std::is_enum_v<EnumT>, "EnumT must be an enum type"); } } } // 调用方式:myLib::setVals<enumVals>(a, b);
关于你提到的思路补充
- 函数模板默认参数与推导指南:如果把
source_location放在模板参数里确实会导致推导问题,但将其作为函数参数的默认参数就可以完全避免,因为函数参数的默认参数不影响模板参数推导。 - source_location默认构造函数:C++20中
std::source_location的默认构造函数不会调用current(),它只会生成一个空的位置信息,因此这个思路不可行,必须通过source_location::current()来获取调用点信息。
内容的提问来源于stack exchange,提问作者ExpertNoob1
相关产品推荐
相关产品推荐

