38

std::stringC++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

これが発生する理由のメカニズムを理解しています。文字列リテラルは実際には null 文字を含む文字の配列であり、範囲ベースの for ループがstd::end()文字配列を呼び出すと、配列の末尾を超えてポインターが取得されます。ヌル文字は配列の一部であるため、ヌル文字を超えるポインターを取得します。

ただし、これは非常に望ましくないと思います。確かにstd::string、文字列リテラルは、長さと同じくらい基本的なプロパティに関しては同じように動作する必要がありますか?

この矛盾を解決する方法はありますか? たとえば、文字配列std::begin()std::end()オーバーロードして、それらが区切る範囲に終端の null 文字が含まれないようにすることはできますか? もしそうなら、なぜこれが行われなかったのですか?

編集:「レガシー機能」であるCスタイルの文字列を使用した結果に苦しんでいると言っている人たちに私の憤りをもう少し正当化するために、次のようなコードを検討してください:

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

あなたは何か違うことを期待f("hello")し、f(std::string("hello"))するでしょうか?

4

6 に答える 6

29

オーバーロードstd::begin()std::end()、const char 配列が配列のサイズよりも 1 小さい値を返す場合、次のコードは予想される 5 ではなく 4 を出力します。

#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';
}
于 2011-07-17T23:23:38.423 に答える
22

ただし、これは非常に望ましくないと思います。確かに std::string と文字列リテラルは、長さと同じくらい基本的なプロパティに関しては同じように動作する必要がありますか?

定義による文字列リテラルには、文字列の末尾に (隠された) null 文字があります。Std::strings にはありません。std::strings には長さがあるため、その null 文字は少し余分です。文字列ライブラリの標準セクションでは、null 以外で終了する文字列を明示的に許可しています。

編集
大量の賛成票と大量の反対票という意味で、これほど物議を醸す回答をしたことはないと思います。

C スタイルの配列に適用されたauto反復子は、配列の各要素を反復処理します。範囲の決定は、実行時ではなくコンパイル時に行われます。これは形式が正しくありません。たとえば、次のようになります。

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

任意のデータを保持するために char 型の配列を使用する人もいます。はい、これは古いスタイルの C の考え方であり、おそらく C++ スタイルの std::array を使用する必要がありましたが、この構成は非常に有効で非常に便利です。char buffer[1024];その要素がたまたま null 文字と同じ値を持っているという理由だけで、自動イテレータが要素 15 で停止した場合、それらの人々はむしろ動揺するでしょう。a に対する自動反復子はType buffer[1024];、最後まで実行されます。char 配列がまったく異なる実装に値する理由は何ですか?

文字配列に対する自動反復子を早期に停止させたい場合は、それを行う簡単なメカニズムがあることに注意してif (c == '0') break;ください。ループの本体にステートメントを追加します。

結論: ここに矛盾はありません。char[] 配列に対するautoイテレータは、自動イテレータが他の C スタイルの配列でどのように動作するかと一貫性があります。

于 2011-07-17T23:21:29.420 に答える
19

6最初のケースで発生するのは、C では回避できなかった抽象化リークです。それstd::stringを「修正」します。互換性のために、C スタイルの文字列リテラルの動作は C++ でも変わりません。

たとえば、文字配列に対して std::begin() と std::end() をオーバーロードして、それらが区切る範囲に終端の null 文字が含まれないようにすることはできますか? もしそうなら、なぜこれが行われなかったのですか?

char[N]文字数を含む文字列内に変数を埋め込むことによってのみ( ではなく) ポインターを介したアクセスを想定しているため、シークNULLはもう必要ありません。おっとっと!それはstd::string

「矛盾を解決する」方法は、従来の機能を一切使用しないことです

于 2011-07-17T23:25:42.623 に答える
6

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) ...

EDIT:以下はオーバーヘッドが小さくなります(ただし、ユーザー定義のリテラルは利用できません)。

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

for (auto e : literal("hello")) ...
于 2011-07-18T07:55:17.487 に答える
4

長さが必要な場合strlen()は、C 文字列と.length()C++ 文字列に使用する必要があります。C 文字列と C++ 文字列を同じように扱うことはできません。動作が異なります。

于 2011-07-17T23:23:04.493 に答える
3

不整合は、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スタイルの文字列リテラルを使用する理由はほぼ間違いなくありません。

于 2011-07-19T04:07:01.947 に答える