使用 constexpr、SFINAE 和/或 type_traits 进行 char*、char 数组和字符串文字的重载解析

问题描述 投票:0回答:2

我遇到了一个有趣的挑战,我花了几个小时试图解决这个挑战,但经过大量研究和多次失败的尝试后,我发现自己在问这个问题。

我想编写 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 数组和字符串文字?

c++ overloading sfinae type-traits enable-if
2个回答
2
投票

[expr.prim.id.unqual]:

[...] 表达式的类型就是标识符的类型。这 结果是由identifier表示的实体。表达式是一个 如果实体是函数、变量或数据成员,则左值 否则右值;如果标识符指定一个,则它是一个位字段 位域 ([dcl.struct.bind]).

因此,给予声明

const char arr[] = "foo";

表达式

arr
是类型
const char[4]
的左值。


[lex.string]/8:

也指普通字符串文字和UTF-8字符串文字 为窄字符串文字。窄字符串文字的类型为“array of n

const char
”,其中 n 是定义的字符串大小 下面,并且具有静态存储时间。

并且根据 [expr.prim.literal]:

A litera 是主要表达方式。其类型取决于其形式。 A 字符串文字是左值;所有其他文字都是纯右值。

因此,表达式

"foo"
const char[4]
类型的左值。


结论:函数无法区分(常量)字符数组和字符串文字。


0
投票

我也一直在研究这个问题以优化编译时字符串文字。虽然我认为在 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 添加后备应该很容易,这样代码仍然有效并可以编译,只是不会从上述优化中受益。

© www.soinside.com 2019 - 2024. All rights reserved.