如何在宏内部访问外层函数的参数?
要实现一个能访问外层函数参数的宏,核心是绕过宏自身的ARGC/ARGVn参数替换机制。这里有两种可靠的方案,分别适配不同的CMake版本:
方案1:利用CMake 3.17+的上下文变量(推荐)
CMake 3.17及以上版本引入了CMAKE_CURRENT_FUNCTION_ARGC、CMAKE_CURRENT_FUNCTION_ARGVn和CMAKE_CURRENT_FUNCTION_ARGN这些特殊变量,它们在宏内部被调用时,会指向调用该宏的外层函数的参数,完全不受宏自身参数的干扰。
下面是一个模仿cmake_parse_arguments(PARSE_ARGV ...)的完整实现:
macro(my_parse_arguments PARSE_ARGV _start_idx _prefix _options _one_value _multi_value) # 校验调用格式 if(NOT "${PARSE_ARGV}" STREQUAL "PARSE_ARGV") message(FATAL_ERROR "my_parse_arguments requires 'PARSE_ARGV' as the first argument") endif() # 获取外层函数的参数信息 set(_func_argc ${CMAKE_CURRENT_FUNCTION_ARGC}) math(EXPR _remaining_args "${_func_argc} - ${_start_idx}") # 提取从_start_idx开始的待解析参数 set(_parse_args "") if(_remaining_args GREATER 0) foreach(_i RANGE ${_start_idx} ${_func_argc}-1) list(APPEND _parse_args "${CMAKE_CURRENT_FUNCTION_ARGV${_i}}") endforeach() endif() # 初始化结果变量 set(${_prefix}_UNPARSED_ARGUMENTS "") foreach(_opt ${_options}) set(${_prefix}_${_opt} FALSE) endforeach() foreach(_key ${_one_value}) unset(${_prefix}_${_key}) endforeach() foreach(_key ${_multi_value}) set(${_prefix}_${_key} "") endforeach() # 核心解析逻辑 set(_current_key "") foreach(_arg ${_parse_args}) # 处理选项(如--verbose) set(_is_option FALSE) foreach(_opt ${_options}) if("${_arg}" STREQUAL "--${_opt}" OR "${_arg}" STREQUAL "-${_opt}") set(${_prefix}_${_opt} TRUE) set(_is_option TRUE) break() endif() endforeach() if(_is_option) set(_current_key "") continue() endif() # 处理单值关键字 list(FIND _one_value "${_arg}" _one_idx) if(_one_idx GREATER -1) set(_current_key "${_arg}") continue() endif() # 处理多值关键字 list(FIND _multi_value "${_arg}" _multi_idx) if(_multi_idx GREATER -1) set(_current_key "${_arg}") continue() endif() # 处理关键字对应的值或未解析参数 if(NOT "${_current_key}" STREQUAL "") list(FIND _one_value "${_current_key}" _check_one) if(_check_one GREATER -1) # 单值关键字:覆盖设置 set(${_prefix}_${_current_key} "${_arg}") set(_current_key "") else() # 多值关键字:追加值 list(APPEND ${_prefix}_${_current_key} "${_arg}") endif() else() # 未匹配的参数 list(APPEND ${_prefix}_UNPARSED_ARGUMENTS "${_arg}") endif() endforeach() endmacro()
使用示例:
function(my_build_tool) # 跳过前2个固定参数,从索引2开始解析后续参数 my_parse_arguments(PARSE_ARGV 2 BUILD "VERBOSE;DEBUG" # 开关选项 "TARGET_NAME;VERSION" # 单值关键字 "SOURCES;LINK_LIBS" # 多值关键字 ) # 打印解析结果 message("Build verbose mode: ${BUILD_VERBOSE}") message("Target name: ${BUILD_TARGET_NAME}") message("Source files: ${BUILD_SOURCES}") message("Unparsed args: ${BUILD_UNPARSED_ARGUMENTS}") endfunction() # 调用函数测试 my_build_tool src include --verbose TARGET_NAME MyApp SOURCES main.cpp utils.cpp LINK_LIBS pthread m extra_flag
运行后输出:
Build verbose mode: TRUE Target name: MyApp Source files: main.cpp;utils.cpp Unparsed args: src;include;extra_flag
方案2:兼容旧版本CMake(3.17以下)
如果需要适配更旧的CMake版本,可以通过间接变量引用的方式,让宏接收外层函数的ARGC/ARGV变量名,从而绕过宏的参数替换:
macro(my_parse_arguments_compat _argc_var _argv_prefix _start_idx _prefix _options _one_value _multi_value) # 通过变量名间接获取外层函数的参数个数 set(_func_argc ${${_argc_var}}) math(EXPR _remaining_args "${_func_argc} - ${_start_idx}") # 提取待解析参数 set(_parse_args "") if(_remaining_args GREATER 0) foreach(_i RANGE ${_start_idx} ${_func_argc}-1) set(_argv_var "${_argv_prefix}${_i}") list(APPEND _parse_args "${${_argv_var}}") endforeach() endif() # 后续解析逻辑和方案1完全一致,此处省略... endmacro()
使用示例:
function(my_old_tool) # 直接传递外层函数的ARGC和ARGV变量名 my_parse_arguments_compat(ARGC ARGV 2 OLD_BUILD "VERBOSE;DEBUG" "TARGET_NAME;VERSION" "SOURCES;LINK_LIBS" ) # ... 处理解析结果 endfunction()
原理说明
宏本质是字符串替换,直接在宏内使用${ARGC}或${ARGVn}会被宏自身的参数替换。而:
- 方案1中的
CMAKE_CURRENT_FUNCTION_*系列变量是CMake维护的上下文变量,它们的名称和宏的参数变量(ARGC/ARGVn)不同,因此不会被替换,能直接指向外层函数的参数。 - 方案2通过传递变量名(而非变量值),再用
${${_var_name}}的间接引用方式,从外层函数作用域中获取真实的参数值。
内容的提问来源于stack exchange,提问作者Kuba hasn't forgotten Monica
相关产品推荐
相关产品推荐

