C++警察のあんたがたにpointer interconvertibleという新たなおもちゃのおしらせです

# C++ Advent Calender 2020

この記事は[C++ Advent Calender 2020](https://qiita.com/advent-calendar/2020/cxx)5日目の記事です。

昨日は @yohhoy 氏の[C++20便利機能の紹介：自動joinスレッドと停止機構 std::jthread, stop_token](https://qiita.com/yohhoy/items/e493aeacef9a1f6315b9)でした
明日は @MusicScience37 氏の[std::tuple の型を実行時に選択したい](https://qiita.com/MusicScience37/items/9dcd9359194f378fd0a7)です

# はじめに

この記事を書いている私は実のところpointer interconvertibleについてさほど詳しく有りません。多分@k_satoda 氏あたりに聞くほうがこの記事よりはるかにわかりやすく正確な回答が帰ってくるであろうことは火を見るより明らかですが、それでもまあ、自分の勉強も兼ねて筆をすすめることにします。

# C++警察御用達のおもちゃ: Strict Aliasing rule

C++警察の皆さんならばつぎのコードが未定義動作であることがわかるはずです。

```cpp
class A { int foo; };
class B { int foo; };

A a;
B* b = reinterpret_cast<B*>(&a);
b->foo = 3;
```

詳しくは
[（翻訳）C/C++のStrict Aliasingを理解する または - どうして#$@##@^%コンパイラは僕がしたい事をさせてくれないの！ - yohhoyの日記](https://yohhoy.hatenadiary.jp/entry/20120220/p1)
に丸投げします。余談ですがC++20でStrict Aliasing ruleの定義が随分とすっきりしました。
[[basic.lval] 11](https://timsong-cpp.github.io/cppwp/n4861/basic.lval#11)

上の例で言うなら、変数`b`に実際にアクセスが行われる`b->foo = 3;`のとき、`b`は`a`へのエイリアスにならないため、未定義動作となるのでした。

では`b->foo = 3;`がなかったらどうでしょうか？実際にアクセスが行われないのでキャストだけではStrict Aliasing ruleに反しません。

じゃあ未定義動作ではないのか？いいえ、実はこの`reinterpret_cast`はpointer interconvertibleではないのです。

# pointer interconvertible

## pointer interconvertibleの定義

規格書の定義を翻訳してみましょう。
[[basic.compound] 4](https://timsong-cpp.github.io/cppwp/n4861/basic.compound#4)

2つのオブジェクト`a`, `b`がpointer interconvertibleであるのは次のいずれかのときである

- 同一のオブジェクトである
- 片方が共用体オブジェクトで、他方がその共用体の非静的メンバ変数である
- 片方がstandard layoutクラスオブジェクトであり、他方は次のいずれかである場合
    - `if (そのオブジェクトが非静的メンバ変数を持つ)`:  
      →そのオブジェクトの最初の非静的メンバ変数
    - `else`:  
      →基底クラスオブジェクト
-  次のようなオブジェクト`c`が存在する: `a`と`c`がpointer interconvertibleでかつ`b`と`c`がpointer interconvertibleである

## pointer interconvertibleの登場場面: `reinterpret_cast`

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">異なるオブジェクト型へのポインタ間のキャストは以下で定義されています。<a href="https://t.co/QqzA00iyhI">https://t.co/QqzA00iyhI</a><br>ここで現れる static_cast について、結果の型の有効なオブジェクトを指すようになる条件が pointer-interconvertible を使って定められています。<a href="https://t.co/9O5hRHfjnt">https://t.co/9O5hRHfjnt</a></p>&mdash; Kazutoshi SATODA (@k_satoda) <a href="https://twitter.com/k_satoda/status/1332934679398236161?ref_src=twsrc%5Etfw">November 29, 2020</a></blockquote>

まず`reinterpret_cast`がオブジェクトへのポインタを*cv* `T*`に変換するとき、次のように振る舞います。([[expr.reinterpret.cast] 7](https://timsong-cpp.github.io/cppwp/n4861/expr.reinterpret.cast#7))

```cpp
static_­cast<cv T*>(static_­cast<cv void*>(v))
```

次に、*cv1* `void*`を*cv2* `T*`に`static_cast`で変換するときを考えます。この変換ができるにはcv修飾子`cv1`, `cv2`を比べた時に同じか`cv1`よりもより修飾されている必要があります(例えば`const`を`static_cast`で外せない、それはそう)([[expr.static.cast] 13](https://timsong-cpp.github.io/cppwp/n4861/expr.static.cast#13))。

最後に*cv1* `void*`を*cv2* `T*`に`static_cast`で変換して得たポインタの値が変換後の型として有効かを考えます。その条件は次のとおりです([[expr.static.cast] 13](https://timsong-cpp.github.io/cppwp/n4861/expr.static.cast#13))。

1. 元となったポインタの値がメモリー上のAというアドレスを指していて、Aが変換後の型`T`のアラインメント要求を満たす  
  満たさない→得られたポインタの値はunspecified
2. 元となったポインタの値が`a`というオブジェクトを指していてかつ、cv修飾子を無視した時に`a`とpointer interconvertibleな`T`型のオブジェクト`b`が存在する  
   満たさない→ポインタの値が変化しない→*cv2* `T*`型のポインタとしては有効ではない[^1]


## 同じアドレスに2つのオブジェクトがあることとpointer interconvertibleであることは別である

```cpp
struct A {
    int aaa;
    double bbb;
};
double foo(int* a)
{
    reinterpret_cast<const A*>(a)->bbb;
}
int main()
{
    A a{3, 4.2};
    std::cout << foo(a.aaa) << std::endl;
}
```

pointer interconvertibleの定義から、クラス`A`へのポインタとクラスAの非静的メンバ変数である`aaa`へのポインタは変換可能です。

このとき、この2つは同じアドレスを指していることから「なるほど、pointer interconvertibleってのはつまり同じアドレスだったら変換できるってことか！」と考える人がいるかも知れません。

```cpp
#include <cassert>
int main(){
    int arr[4];
    void* ptr1 = static_cast<void*>(arr);// OK
    void* ptr2 = static_cast<void*>(&arr);// OK
    assert(ptr1 == ptr2); // => true, same adress
    auto ptr1_2 = static_cast<int (*)[4]>(ptr2);// NG: not pointer interconvertible
    auto ptr2_2 = static_cast<int*>(ptr1);// NG: not pointer interconvertible
}
```

しかし上の例を見てください。変数`ptr1_2`/`ptr2_2`を初期化しようとしているcastはpointer interconvertibleではありません。言い換えると、配列オブジェクトと配列の先頭要素は同じアドレスにありますが、pointer interconvertibleではありません。

配列だけが例外と考えるのではなく、むしろpointer interconvertibleという関係性そのものが歴史的背景などからくる特殊例と考えるべきでしょう。

# 経緯

pointer interconvertibleのどこが新しいねん！というツッコミが来そうなのでこの記事を書くに至った経緯を書いておくことにします。

先日[Qiitaに宣伝記事が出ていた](https://qiita.com/rinatz/items/05492be4eea28f6598da)C++入門サイトがあります
https://rinatz.github.io/cpp-book/
このサイトの査読をするうちに、キャストの説明に改善点がみつかり、ついでにStrict Aliasing Ruleの解説を書き加えました。
https://github.com/rinatz/cpp-book/pull/67
しかしその過程でStrict Aliasing Ruleの解釈が不安になりteratailとTwitterで質問をしました。
[C++ - strict alias rule違反となるのはキャストした時点か｜teratail](https://teratail.com/questions/306235)

解釈についてあっていることが確認できてホッとしたところにC++有識者の @gnaggnoyil 氏からこんな指摘がありました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">そもそも元スレのreinterpret_cast式は現在のWDによるといわゆるstrict aliasing ruleで定義されてません。pointer interconvertibleを満たさない場合には意味がすでに必要とされてないため、文面通りの「未定義行為」です。</p>&mdash; g_naggnoyil(gint, gchar **) (@gnaggnoyil) <a href="https://twitter.com/gnaggnoyil/status/1332244746937409536?ref_src=twsrc%5Etfw">November 27, 2020</a></blockquote>

pointer interconvertibleって何？？？

私にとって未知な用語に触れた私は調査を開始するのでした。

ちょうどほぼ同時並行して @yohhoy 氏がcpprefjpに`is_pointer_interconvertible_base_of`についての項目を執筆されているのに励まされながら、遅刻しつつもこの記事は執筆されました。
https://github.com/cpprefjp/site/commit/373db2562dd489679aa96001078a6cb320052725

# 追記: `is_pointer_interconvertible_base_of`の存在意義が問われる事態

https://github.com/cpprefjp/site/issues/824
でその存在意義に疑問符がつき
https://lists.isocpp.org/std-discussion/2020/12/0921.php
std-discussionに質問が飛んで
https://lists.isocpp.org/std-discussion/2020/12/0922.php
[p0466](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0466r5.pdf)の作者にメンションが飛んでる模様です

# 参考資料

- [C++ - strict alias rule違反となるのはキャストした時点か｜teratail](https://teratail.com/questions/306235)
- [（翻訳）C/C++のStrict Aliasingを理解する または - どうして#$@##@^%コンパイラは僕がしたい事をさせてくれないの！ - yohhoyの日記](https://yohhoy.hatenadiary.jp/entry/20120220/p1)
- [c++ - Pointer interconvertibility vs having the same address - Stack Overflow](https://stackoverflow.com/questions/47924103/pointer-interconvertibility-vs-having-the-same-address)
- [Questions on N4430 (Core Issue 1776: Replacement of class objects containing reference members)](https://groups.google.com/a/isocpp.org/g/std-proposals/c/gN-_7CJ58G4/m/4JG3i4S25z8J)
- [oop - Object-orientation in C - Stack Overflow](https://stackoverflow.com/questions/415452/object-orientation-in-c/415536#415536)
- [c++ - Casting a char array to an object pointer - is this UB? - Stack Overflow](https://stackoverflow.com/questions/51231757/casting-a-char-array-to-an-object-pointer-is-this-ub)
- [std::is_pointer_interconvertible_base_of - cppreference.com](https://en.cppreference.com/w/cpp/types/is_pointer_interconvertible_base_of)
- [is_pointer_interconvertible_base_of - cpprefjp C++日本語リファレンス](https://cpprefjp.github.io/reference/type_traits/is_pointer_interconvertible_base_of.html)
- [P0593R6: Implicit creation of objects for low-level object manipulation#practical-examples](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html#practical-examples)
- [Accessing Object Representations - p1839r2.pdf](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1839r2.pdf)
- [std::launder関数 - yohhoyの日記](https://yohhoy.hatenadiary.jp/entry/20170817/p1)
- https://twitter.com/k_satoda/status/1242300634587467777

<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

[^1]: [[expr.static.cast] 13](https://timsong-cpp.github.io/cppwp/n4861/expr.static.cast#13)には変換によってポインタの値が変化しないとしか書かれてないけど、多分もっと規格書をたどれば有効じゃないってことになる気がする、調べるには時間が足りなかった。