如何判断C/C++函数或宏的参数是否为常量字符串?
怎么检测宏/函数的输入是常量字符串还是变量
在C里,要区分传给宏或函数的参数是常量字符串字面量(比如"hello"这种直接写的字符串)还是变量(比如std::string y或者const char* ptr这种),可以用预处理阶段的编译器扩展技巧,或者编译阶段的标准C方法,下面分两种情况说明:
一、用宏实现(依赖编译器扩展)
宏是纯文本替换,标准C++预处理器没法直接判断是不是字符串字面量,但GCC、Clang这类编译器有个内置扩展__builtin_constant_p,能检测参数是不是编译时常量。不过要注意,这个是编译器专属的,跨平台可能用不了:
#include <stdio.h> #define SOME_MACRO(x) do { \ if (__builtin_constant_p(x)) { \ printf("x is a constant string\n"); \ } else { \ printf("x is a variable\n"); \ } \ } while(0) int main() { SOME_MACRO("This is a constant string"); // 输出:x is a constant string int x = 2; std::string y = std::to_string(x); SOME_MACRO(y.c_str()); // 输出:x is a variable const char* var_ptr = "test"; SOME_MACRO(var_ptr); // 输出:x is a variable(因为var_ptr是变量,哪怕它指向字面量) return 0; }
提醒一下:__builtin_constant_p会把所有编译时常量都当成常量,比如传个5进去也会判定成常量,所以如果要严格只识别字符串字面量,这个方法有局限。
二、标准C++编译时实现(跨平台兼容)
如果要严格区分字符串字面量和变量,而且要符合标准C++,可以用模板重载或者constexpr来实现:
方法1:利用字符串字面量的数组类型
字符串字面量的本质是const char[N]类型的数组(N是字符串长度加1,包含末尾的终止符),而变量比如std::string或者const char*的类型和它不一样。我们可以写两个模板函数,分别匹配数组类型和其他类型:
#include <iostream> #include <string> // 专门匹配字符串字面量(const char[N]类型) template <size_t N> void detect(const char (&str)[N]) { std::cout << "x is a constant string" << std::endl; } // 匹配所有其他类型(变量) template <typename T> void detect(const T& var) { std::cout << "x is a variable" << std::endl; } // 封装成宏,保持你想要的调用方式 #define SOME_MACRO(x) detect(x) int main() { SOME_MACRO("This is a constant string"); // 匹配数组模板,输出常量提示 int x = 2; std::string y = std::to_string(x); SOME_MACRO(y); // 匹配通用模板,输出变量提示 const char* var_ptr = "test"; SOME_MACRO(var_ptr); // 匹配通用模板,输出变量提示 return 0; }
这个方法是标准C++兼容的,能精准区分:
- 传
"xxx"这种字面量时,会匹配第一个模板 - 传
std::string对象、const char*变量这类,会匹配第二个通用模板
方法2:用constexpr在编译时判断
如果想在编译阶段就完成判断,避免运行时开销,可以结合C++17的if constexpr和类型判断:
#include <iostream> #include <string> #include <type_traits> constexpr bool is_constant_string(const auto& arg) { // 判断参数是不是char数组(也就是字符串字面量) return std::is_array_v<std::remove_reference_t<decltype(arg)>> && std::is_same_v<std::remove_const_t<std::remove_extent_t<std::remove_reference_t<decltype(arg)>>>, char>; } #define SOME_MACRO(x) do { \ if constexpr (is_constant_string(x)) { \ std::cout << "x is a constant string" << std::endl; \ } else { \ std::cout << "x is a variable" << std::endl; \ } \ } while(0) int main() { SOME_MACRO("This is a constant string"); // 编译时就判定为常量 std::string y = "test"; SOME_MACRO(y); // 编译时判定为变量 const char* ptr = "test"; SOME_MACRO(ptr); // 编译时判定为变量 return 0; }
这个方法在编译时就确定走哪个分支,不会生成没用的代码,效率更高。
注意点
- 要是传的是
const char*类型的变量,哪怕它指向字符串字面量,也会被判定成变量——因为变量本身不是字面量 - 预处理阶段的方法依赖编译器扩展,跨平台性差;编译阶段的方法是标准写法,更推荐用
内容的提问来源于stack exchange,提问作者Lake Pewaukee
相关产品推荐
相关产品推荐

