C,C++規格冒険記1:std::string::substr

# リンク

0回目（初回）：[こちら](http://qiita.com/L3Sota/items/68f7d0c1612e96e3ad26)
1回目（今回）：こ↑こ↓
2回目（次回）：まだ無い

# はじめに

**これなに？？？**という人は0回目のを読んでね！
相変わらずくだらない理由で規格を漁っているL3Sotaです。先日ECMAScript2016の規格もダウンロードしたのでJavascriptもやる可能性が出てきました。
さて、今回は大したことない問題で盛大にグダリます。
お題はというと、こんな感じのコードが発端です：

```cpp
#include <iostream>
#include <string>

int main(void) {
    std::string s = "Hello";
    std::cout << s.substr(0) << '\n'; // "Hello"
    std::cout << s.substr(4) << '\n'; // "o"
    std::cout << s.substr(5);         // ""
    std::cout << std::endl;
    return 0;
}
```

まあこれ我が家のgccでもClangでも普通にコンパイルしました。あれ、でも最後のやつって `s.substr(s.size());` だよね？大丈夫？？

はい、**気になったら規格を読もう！**とりあえず`substr`を見ましょう！

と、その前に：

# `string` は `basic_string`！

まず `std::string::substr` ってそのままだと無いんですよね。 `string` っていうクラス直書きではまず存在しないんですけど。探してみれば `std::basic_string::substr` ならあるんですけど…。（むーりぃー
じゃあ `string` の定義って何だっけということになりますが、安心してください、**規格にちゃんと書いてあります。**

N3242 (p. 623), N3797 (p. 632):

> 21.3 String classes [string.classes]
> The header `<string>` defines the `basic_string` class template [. . .] and four typedefs, `string`, `u16string`, `u32string`, and `wstring`, that name the specializations `basic_string<char>`, `basic_string<char16_t>`, `basic_string<char32_t>`, and `basic_string<wchar_t>`, respectively.

N3242 (p. 627), N3797 (p. 634):

```cpp
typedef basic_string<char> string;
typedef basic_string<char16_t> u16string;
typedef basic_string<char32_t> u32string;
typedef basic_string<wchar_t> wstring;
```

つまり `basic_string` をテンプレートとして、普段使っている `string` はテンプレート引数に普通の `char` を放り込んだ「特殊化」(specialization)というやつです。なので残りの話は全部 `basic_string` で行えばいいということです。やったね！
ほんなら早速、遠慮なく：

# `basic_string::substr`

N3242 (p. 649), N3797 (p. 657):

> 21.4.7.8 `basic_string::substr` [string::substr]

```cpp
basic_string<charT,traits,Allocator>
 substr(size_type pos = 0, size_type n = npos) const;
```
 
> _Requires:_ `pos <= size()`
> _Throws:_ `out_of_range` if `pos > size()`.
> _Effects:_ Determines the effective length `rlen` of the string to copy as the smaller of `n` and `size() - pos`.
> _Returns:_ `basic_string<charT,traits,Allocator>(data()+pos,rlen)`.

一応 `pos == size()` はセーフらしいです。また、この場合は `rlen == 0` になりますね。 `basic_string<charT,traits,Allocator>(data()+pos,rlen)` を読み解く必要がありますが、まず `basic_string` という名前なので constructor です。引数の `data()` は `string` の中身の配列でしょうが、記事の完成度の為にもちゃんと規格を見ましょう：

## `basic_string::data`

N3242 (p. 645), N3797 (p. 654)

> 21.4.7.1 `basic_string` accessors [string.accessors]

```cpp
const charT* c_str() const noexcept;
const charT* data() const noexcept;
```

> _Returns:_ A pointer `p` such that `p + i == &operator[](i)` for each `i` in `[0,size()]`.
> _Complexity:_ constant time.
> _Requires:_ The program shall not alter any of the values stored in the character array.

配列、もといポインターが返ってくるようです。そして！どうやら **`i == size()` でもまともな値が返ってくる**ことが保証されています。詳しい挙動は
 `operator[]` に盥廻しされてるので次はそっちを見ます：

## `basic_string::operator[]`

N3242 (p. 637):

> 21.4.5 `basic_string` element access [string.access]

```cpp
const_reference operator[](size_type pos) const noexcept;
reference       operator[](size_type pos) noexcept;
```

> _Requires:_ `pos <= size()`.
> _Returns:_ `*(begin() + pos)` if `pos < size()`. Otherwise, returns a reference to an object of type `charT` with value `charT()`, where modifying the object leads to undefined behavior.
> _Complexity:_ constant time.

N3797 (p. 646) 差分:

```cpp
const_reference operator[](size_type pos) const;
reference       operator[](size_type pos);
```

> _Returns:_ [. . .]
> _Throws:_ Nothing.
> _Complexity:_ [. . .]

規格の間に若干差が出ていますね。N3797では `noexcept` が削られ、_Throws:_の部分が追記されています。Exception投げないんで存在意義微妙ですけど。何はともあれ、どうやら `operator[]` は `pos==size()` ならば
`charT()` の値を返すらしいです。 `string` の場合は即ち `char` のことなので、え～なんともややこしいですね。Primitive type ですし。この辺は初期化の話になってくるので**次回の記事に回しましょう！**（え
結論だけ言わないと次に進めないのでそこまで飛ばしますが、結果としては値はゼロになる、とのことです。つまり、 `substr` の返り値の表現にあった `data()+pos` は `'\0'` へのポインターであることがわかる、ということです。

さて、このポインターと `rlen==0` を `basic_string<charT,traits,Allocator>(data()+pos,rlen)` に放り込んだらどうなるのでしょう？規格を見ましょう：

# `basic_string::basic_string`

N3242 (pp. 633-634):

> 21.4.2 `basic_string` constructors and assignment operators [string.cons]

```cpp
basic_string(const charT* s, size_type n,
             const Allocator& a = Allocator());
```

> _Requires:_ `s` shall not be a null pointer and `n < npos`.
> _Effects:_ Constructs an object of class `basic_string` and determines its initial string value from the array of `charT` of length `n` whose first element is designated by `s`, as indicated in Table 66.

Table 66 — `basic_string(const charT*, size_type, const Allocator&)` effects

| Element      | Value |
|:-------------|:------|
| `data()`     | points at the first element of an allocated copy of the array whose first element is pointed at by `s` |
| `size()`     | `n` |
| `capacity()` | a value at least as large as `size()` |

N3797 (pp. 641-642) 差分:

> _Requires:_ `s` points to an array of at least `n` elements of `charT`.

`'\0'` がコピーされるかどうかは微妙なところですが、長さゼロの `string` はちゃんと作られるっぽいです。コピーされるか微妙と言っているのは何も不思議な話ではなく、"array of `charT` of length `n`"という表現がそもそも意味不明であることに起因します。全く同じわけではありませんが、規格では配列の宣言に関して以下の制約が明記されています：

# 配列の長さは正（それはそう）

N3242 (p. 188), N3797 (p. 187):

> 8.3.4 Arrays [dcl.array]

> In a declaration `T D` where `D` has the form
> `D1 [` *constant-expression_opt* `]` *attribute-specifier-seq_opt*
> [. . .]. If the _constant-expression_ (5.19) is present, it shall be an integral constant expression and its value shall be greater than zero.

N3797 (p. 187) 差分: 
> If the _constant-expression_ (5.19) is present, it shall be a converted constant expression of type `std::size_t` and its value shall be greater than zero.

はい、長さゼロの配列は宣言できまーーーせん！まあコードの中では当然の話ですよね。規格の英語でたまたま長さゼロの配列という表現になってしまったからといって超困るわけではありませんが、少なくとも `'\0'` がコピーされているかどうかはわかりませんね。

とりあえずこれで冒頭の `s.substr(5)` に問題は無いことがわかりました。めでたしめでたし。ただこれはこれでまだ調べ甲斐のありそうな点があります。何かと言えばそう、長さゼロの `string` をそのまま出力しても大丈夫なことです。

# 謎が謎を呼ぶ（跋文）

発端となったコードが完全に正しいか確認するためには、あとは長さゼロの `string` を出力できるかどうかにかかっているのですが、記事名が `substr` なのに内容ぶっ飛びすぎじゃね？という懸念（及び今日は書き疲れたという大変深刻な問題）により別記事に分けようと思います。初期化の話も積み残しているのでやる事はまだまだありそうです。とりあえずここまでお付き合いいただきありがとうございます。
自分から規格読むとか言っておきながらアレですが、割と規格をMarkdownに整形するのがダルいので眠くて解説を間違えていたりするかもしれません。その場合はコメントか[Twitter](https://twitter.com/L3Sota)にでも文句を書いてあげてください。可及的気が向いたら直します。
では、次の記事が満足いく形になるまで。