Несоответствие между std :: string и строковыми литералами - PullRequest
38 голосов
/ 18 июля 2011

Я обнаружил тревожное несоответствие между std::string и строковыми литералами в 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;
}

Вывод:

Number of elements: 6
Number of elements: 5

Я понимаю, почемуэто происходит: строковый литерал на самом деле представляет собой массив символов, который содержит нулевой символ, и когда основанный на диапазоне цикл вызывает std::end() для массива символов, он получает указатель за концом массива;поскольку нулевой символ является частью массива, он, таким образом, получает указатель после нулевого символа.

Однако я считаю, что это очень нежелательно: конечно, std::string и строковые литералы должны вести себя одинаково, когда дело доходит добазовые свойства как их длина?

Есть ли способ устранить это несоответствие?Например, могут ли std::begin() и std::end() быть перегружены для символьных массивов, чтобы диапазон, который они разделяют, не включал завершающий нулевой символ?Если так, почему это не было сделано?

РЕДАКТИРОВАТЬ : Чтобы еще больше оправдать мое негодование тем, кто сказал, что я просто страдаю от последствий использования строк в стиле C, которыеявляются "устаревшей функцией", рассмотрите код, подобный следующему:

template <typename Range>
void f(Range&& r)
{
    for (auto e : r)
    {
        ...
    }
}

Ожидаете ли вы, что f("hello") и f(std::string("hello")) сделают что-то другое?

Ответы [ 6 ]

29 голосов
/ 18 июля 2011

Если бы мы перегружали std::begin() и std::end() для массивов const, чтобы они возвращали единицу меньше размера массива, то следующий код вывел бы 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';
}
21 голосов
/ 18 июля 2011

Однако я думаю, что это очень нежелательно: конечно, std :: string и строковые литералы должны вести себя одинаково, когда дело доходит до свойств, таких же базовых, как их длина?

Строковые литералы по определению имеют (скрытый) нулевой символ в конце строки. Std :: строк нет. Поскольку std :: strings имеют длину, этот нулевой символ немного лишний. Стандартный раздел библиотеки строк явно разрешает строки с ненулевым завершением.

Редактировать
Я не думаю, что когда-либо давал более противоречивый ответ в смысле огромного количества отрицательных голосов и огромного количества отрицательных голосов.

Итератор auto при применении к массиву в стиле C выполняет итерации по каждому элементу массива. Определение диапазона производится во время компиляции, а не во время выполнения. Это плохо сформировано, например:

char * str;
for (auto c : str) {
   do_something_with (c);
}

Некоторые люди используют массивы типа char для хранения произвольных данных. Да, это стиль мышления в старом стиле C, и, возможно, им следовало бы использовать std :: array в стиле C ++, но конструкция вполне допустима и весьма полезна. Эти люди были бы весьма расстроены, если бы их автоматический итератор над char buffer[1024]; остановился на элементе 15 только потому, что этот элемент имеет то же значение, что и нулевой символ. Автоматический итератор над Type buffer[1024]; будет работать до конца. Что делает массив char настолько достойным совершенно другой реализации?

Обратите внимание, что если вы хотите, чтобы автоматический итератор над массивом символов останавливался раньше, есть простой механизм сделать это: добавить оператор if (c == '0') break; в тело вашего цикла.

Итог: здесь нет противоречий. Итератор auto над массивом char [] согласуется с тем, как автоматический итератор работает с любым другим массивом в стиле C.

19 голосов
/ 18 июля 2011

То, что вы получите 6 в первом случае, - это утечка абстракции, которую невозможно избежать в C. std::string "исправляет" это. Для совместимости поведение строковых литералов в стиле C не меняется в C ++.

Например, могут ли быть перегружены std :: begin () и std :: end () для массивы символов, так что диапазон, который они разделяют, не включает завершающий нулевой символ? Если так, почему это не было сделано?

Предполагается доступ через указатель (в отличие от char[N]), только путем встраивания переменной в строку, содержащую количество символов, так что поиск NULL больше не требуется. К сожалению! Это std::string.

Способ "устранить несоответствие" - это вообще не использовать устаревшие функции .

6 голосов
/ 18 июля 2011

В соответствии с N3290 6.5.4, если диапазон является массивом, граничные значения инициализируются автоматически без отправки функции begin / end.
Итак, как насчет подготовки некоторой обертки, подобной следующей?

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 );
};

Тогда будет действителен следующий код:

for (auto e : literal("hello")) ...

Если ваш компилятор предоставляет пользовательский литерал, может помочь сокращение:

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

for (auto e : "hello"_l) ...

РЕДАКТИРОВАТЬ: Следующие будут иметь меньшие издержки (пользовательский литерал не будет доступен, хотя).

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

for (auto e : literal("hello")) ...
4 голосов
/ 18 июля 2011

Если вам нужна длина, вы должны использовать strlen() для строки C и .length() для строки C ++.Нельзя одинаково обращаться со строками C и C ++ - они ведут себя по-разному.

3 голосов
/ 19 июля 2011

Несоответствие можно устранить с помощью другого инструмента в наборе инструментов C ++ 0x: пользовательских литералов. Используя правильно определенный пользовательский литерал:

std::string operator""s(const char* p, size_t n)
{
    return string(p, n);
}

Мы сможем написать:

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

Который теперь выводит ожидаемое число:

Number of elements: 5

С этими новыми литералами std :: string, пожалуй, больше нет причин использовать строковые литералы в стиле C, когда-либо.

...