Inconsistency between std::string and string literals

HC4*_*ica 38 c++ string foreach string-literals c++11

I have discovered a disturbing inconsistency between std::string and string literals in C++0x:

#include <iostream>
#include <string>

int main()
{
    int i = 0;
    for (auto e : "hello")
        ++i;
    std::cout << "Number of elements: " << i << '\n';

    i = 0;
    for (auto e : std::string("hello"))
        ++i;
    std::cout << "Number of elements: " << i << '\n';

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

The output is:

Number of elements: 6
Number of elements: 5
Run Code Online (Sandbox Code Playgroud)

I understand the mechanics of why this is happening: the string literal is really an array of characters that includes the null character, and when the range-based for loop calls std::end() on the character array, it gets a pointer past the end of the array; since the null character is part of the array, it thus gets a pointer past the null character.

However, I think this is very undesirable: surely std::string and string literals should behave the same when it comes to properties as basic as their length?

Is there a way to resolve this inconsistency? For example, can std::begin() and std::end() be overloaded for character arrays so that the range they delimit does not include the terminating null character? If so, why was this not done?

EDIT: To justify my indignation a bit more to those who have said that I'm just suffering the consequences of using C-style strings which are a "legacy feature", consider code like the following:

template <typename Range>
void f(Range&& r)
{
    for (auto e : r)
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

Would you expect f("hello") and f(std::string("hello")) to do something different?

How*_*ant 29

如果我们重载std::begin()并且std::end()const char数组返回的数据小于数组的大小,那么下面的代码将输出4而不是预期的5:

#include <iostream>

int main()
{
    const char s[5] = {'h', 'e', 'l', 'l', 'o'};
    int i = 0;
    for (auto e : s)
        ++i;
    std::cout << "Number of elements: " << i << '\n';
}
Run Code Online (Sandbox Code Playgroud)

  • 任何解决方案将需要解决如何处理`常量字符S [6] = { 'H', 'E', 'L', 'L', '0', '\ 0'}做;`.我在这里与霍华德站在一起,C++程序员应该知道`sizeof("你好")== 6` (9认同)
  • 也许有一种方法可以区分由正常定义的字符数组定义为字符串文字的字符数组?我们只想为前者过载. (3认同)
  • @ HighCommander4:我使用`sizeof("Hello")== 6`作为一种快速的方式来编写C语言和C++,字符串文字_are_常量char数组,长度为N + 1,包括终止\ 0.编译器在进行参数重载时不需要,也可能不区分这两者.这意味着您将强制主要编译器重新设计一个次要功能. (2认同)
  • 我才意识到这比那更糟.一个翻译单元可以定义`char const s [6] ="Hello";`而另一个可以调用`end(s)-begin(s)`.这意味着字符串文字和字符串arrys之间的差异将需要ABI更改.对不起,这不会发生. (2认同)

Dav*_*men 21

但是,我认为这是非常不受欢迎的:当涉及到属性作为基本长度时,std :: string和字符串文字肯定应该表现相同吗?

根据定义,字符串文字在字符串的末尾有一个(隐藏的)空字符.Std :: strings没有.因为std :: strings有一个长度,所以null字符有点多余.字符串库上的标准部分显式允许非空终止字符串.

编辑
我认为在大量的投票和大量的投票中,我从未给出过更具争议性的答案.

auto应用于C样式数组时,迭代器迭代数组的每个元素.范围的确定是在编译时进行的,而不是在运行时进行的.这是不正确的,例如:

char * str;
for (auto c : str) {
   do_something_with (c);
}
Run Code Online (Sandbox Code Playgroud)

有些人使用char类型的数组来保存任意数据.是的,这是一种旧式的C思维方式,也许他们应该使用C++风格的std :: array,但是这个结构非常有效且非常有用.如果他们的自动迭代器char buffer[1024];停止在元素15上,那些人会非常不高兴,因为该元素碰巧与null字符具有相同的值.一个自动迭代器Type buffer[1024];将一直运行到最后.什么使char数组如此值得完全不同的实现?

请注意,如果您希望字符数组上的自动迭代器提前停止,则可以使用一种简单的机制:if (c == '0') break;向循环体添加语句.

底线:这里没有矛盾.在auto超过一个char []数组迭代器是自动的迭代器是如何工作的任何其他C数组一致.

  • 这个答案只是重复了提问者在问题中所说的内容,它根本没有解决这个问题(见最后一段) (6认同)

Lig*_*ica 19

6在第一种情况下得到的是一个抽象泄漏,在C语言中无法避免.std::string"修复"了.为了兼容性,C风格的字符串文字的行为在C++中不会改变.

例如,std :: begin()和std :: end()是否可以为字符数组重载,以便它们分隔的范围不包括终止空字符?如果是这样,为什么没有这样做?

假设通过指针(相对于char[N])进行访问,只需在包含字符数的字符串中嵌入变量,这样NULL就不再需要搜索了.哎呀!那是std::string.

"解决不一致"的方法不是使用遗留功能.

  • "根本不使用传统功能." 不使用字符串文字似乎是一项艰巨的任务(并且必须记住字符串文字是"遗留"特征也可能是一项艰巨的任务). (6认同)

Ise*_*ria 6

根据N3290 6.5.4,如果范围是一个数组,边界值会自动初始化而不需要begin/ endfunction dispatch.
那么,如何准备如下的包装?

struct literal_t {
    char const *b, *e;
    literal_t( char const* b, char const* e ) : b( b ), e( e ) {}
    char const* begin() const { return b; }
    char const* end  () const { return e; }
};

template< int N >
literal_t literal( char const (&a)[N] ) {
    return literal_t( a, a + N - 1 );
};
Run Code Online (Sandbox Code Playgroud)

那么以下代码将是有效的:

for (auto e : literal("hello")) ...
Run Code Online (Sandbox Code Playgroud)

如果您的编译器提供用户定义的文字,则缩写可能有所帮助:

literal operator"" _l( char const* p, std::size_t l ) {
    return literal_t( p, p + l ); // l excludes '\0'
}

for (auto e : "hello"_l) ...
Run Code Online (Sandbox Code Playgroud)

编辑:以下将有较小的开销(虽然用户定义的文字将不可用).

template< size_t N >
char const (&literal( char const (&x)[ N ] ))[ N - 1 ] {
    return (char const(&)[ N - 1 ]) x;
}

for (auto e : literal("hello")) ...
Run Code Online (Sandbox Code Playgroud)


HC4*_*ica 3

可以使用 C++0x 工具箱中的另一个工具来解决不一致问题:用户定义的文字。使用适当定义的用户定义文字:

std::string operator""s(const char* p, size_t n)
{
    return string(p, n);
}
Run Code Online (Sandbox Code Playgroud)

我们将能够写:

int i = 0;     
for (auto e : "hello"s)         
    ++i;     
std::cout << "Number of elements: " << i << '\n';
Run Code Online (Sandbox Code Playgroud)

现在输出预期的数字:

Number of elements: 5
Run Code Online (Sandbox Code Playgroud)

有了这些新的 std::string 文字,可以说再也没有理由使用 C 风格的字符串文字了。

  • 注意:用户定义的文字**必须**以下划线开头。另外,另一个答案已经建议了文字 - 为什么不接受那个呢? (4认同)