我遇到了一个有趣的挑战,我花了几个小时试图解决这个挑战,但经过大量研究和多次失败的尝试后,我发现自己在问这个问题。
我想编写 3 个重载函数,每个函数都采用以下类型之一:
const char*
、const char(&)[N]
和 string literal (e.g. "BOO")
。我知道字符串文字只是一个字符数组,但请耐心等待我解释我的方法。
由于包装类
const char*
,下面的两个函数能够区分前两种类型(const char(&)[N]
和CharPtrWrapper
):
#include <iostream>
class CharPtrWrapper
{
public:
CharPtrWrapper(const char* charPtr)
: m_charPtr(charPtr)
{
}
const char * m_charPtr;
};
void processStr(CharPtrWrapper charPtrWrapper)
{
std::cout << "From function that takes a CharPtrWrapper = " << charPtrWrapper.m_charPtr << '\n';
}
template<std::size_t N>
void processStr(const char (&charArr)[N])
{
std::cout << "From function that takes a \"const char(&)[N]\" = " << charArr << '\n';
}
int main()
{
const char* charPtr = "ABC";
processStr(charPtr);
const char charArr[] = {'X', 'Y', 'Z', '\0'};
processStr(charArr);
}
输出:
From function that takes a CharPtrWrapper = ABC
From function that takes a "const char(&)[N]" = XYZ
现在,如果我使用字符串文字(例如
processStr
)调用 processStr("BOO")
,则会调用采用 const char(&)[N]
的版本,这是有道理的,因为字符串文字只是一个字符数组。
这就是我遇到问题的症结所在。我无法编写一个能够区分字符数组和字符串文字的函数。我认为可能有用的一件事是编写一个采用右值引用的版本:
template<std::size_t N>
void processStr(const char (&&charArr)[N])
{
std::cout << "From function that takes a \"const char(&&)[N]\" = " << charArr << '\n';
}
但事实证明字符串文字是左值。我还玩过使用
std::enable_if
和 std::is_array
的不同版本,但我仍然没有得到我想要的结果。
所以我想我的问题如下:现代 C++ 中是否可以区分 char 数组和字符串文字?
[...] 表达式的类型就是标识符的类型。这 结果是由identifier表示的实体。表达式是一个 如果实体是函数、变量或数据成员,则左值 否则右值;如果标识符指定一个,则它是一个位字段 位域 ([dcl.struct.bind]).
因此,给予声明
const char arr[] = "foo";
表达式
arr
是类型 const char[4]
的左值。
也指普通字符串文字和UTF-8字符串文字 为窄字符串文字。窄字符串文字的类型为“array of n
”,其中 n 是定义的字符串大小 下面,并且具有静态存储时间。const char
并且根据 [expr.prim.literal]:
A litera 是主要表达方式。其类型取决于其形式。 A 字符串文字是左值;所有其他文字都是纯右值。
因此,表达式
"foo"
是 const char[4]
类型的左值。
结论:函数无法区分(常量)字符数组和字符串文字。
我也一直在研究这个问题以优化编译时字符串文字。虽然我认为在 C++ 中没有任何标准方法可以做到这一点,但我发现在 GCC 和 Clang 中,您至少可以使用
__builtin_constant_p
内在来确定值是否恒定。
请注意,即使使用模板化大小(通常不支持复数值),内在函数也不适用于整个数组,并且它不能用于指针或引用本身(因为内存位置不是恒定的并且仅确定)链接后),所以技巧是手动遍历字符串并验证每个字符是否是常量:
#include <cstdlib>
constexpr bool is_const_str(const char *s) {
for (; *s; s++) {
if (!__builtin_constant_p(*s)) return false;
}
return true;
}
bool test_dynamic(const char *s) {
return is_const_str(s);
}
bool test_const() {
return is_const_str("Hello, world!");
}
这里有一个 Godbolt 链接,演示了在 GCC 和 Clang 中的工作原理:
https://clang.godbolt.org/z/ch89Wd8Yb
您可以在两个程序集中看到
test_const
被折叠为 1
(如 true
),而 test_dynamic
被折叠为对第一个字符的简单检查(也就是说,如果字符串为空,则它是视为常数,否则不是):
test_dynamic(char const*): # @test_dynamic(char const*)
cmp byte ptr [rdi], 0
sete al
ret
test_const(): # @test_const()
mov al, 1
ret
我找不到 MSVC 替代方案,但至少为始终返回 false 的 MSVC 添加后备应该很容易,这样代码仍然有效并可以编译,只是不会从上述优化中受益。