You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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");  // 非法调用,输出调用处行号
}

方案说明

  1. source_location作为默认参数:将std::source_location设为函数的默认参数,调用时编译器会自动注入当前调用点的位置,完美替代宏中的__LINE__。
  2. 模板重载与约束:通过模板重载和requires约束区分合法/非法调用,合法版本要求所有参数都是目标枚举类型的引用,非法版本处理类型不匹配或无参数的情况。
  3. 简洁API保留:通过宏SET_VALS_FOR_ENUM绑定具体枚举类型,生成的setVals仍支持setVals(a,b)的简洁调用形式。
  4. 友好错误提示:非法调用时会输出调用点行号和自定义提示,比编译器默认错误信息更直观;同时用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 03:05:49