初めに
類似な型は、std::launder
の要件の中などに登場する用語です。ここで、std::launder
はplacement new
構文を使用して、参照型やconst
データメンバを含む構造体/クラスのオブジェクトを再構築するケースで、オブジェクト生存期間(lifetime)に基づいた最適化の抑止をコンパイラに伝える関数です。そして、std::launder
の要件には、
namespace std {
template<class T>
[[nodiscard]] constexpr T* launder(T* p) noexcept;
}
においてアクセスしたいオブジェクト X の型が T に類似(similar)している、があります。この類似(similar)の定義は[conv.qual]/2にあるようです。これを読み解くと、類似(similar)の意味は「オブジェクト X の型と T それぞれで、全てのレベルの cv 修飾を除去した型が等しい」となるようなのですが、なぜそうなるのかがよくわかりませんでした。そこで、この記事では同じような疑問を持った方のための簡単な解説を書きます。
[9/13 : yoh さんにご指摘をいただき、内容を大きく変更しました。以前のものを読んで頂いた方は、もう一度読んでいただければ幸いです。m(> <*)m]
類似(similar)の定義
関連すると思われる記述を次に貼り付けます。
N4830 [conv.qual]
- A cv-decomposition of a type T is a sequence of $cv_i$ and $P_i$ such that T is
“$cv_0$ $P_0$ $cv_1$ $P_1$ $\cdots$ $cv_{n-1}$ $P_{n-1}$ $cv_n$ $U$“ for $n \geq 0$,
where each $cv_i$ is a set of cv-qualifiers, and each $P_i$ is “pointer to”, “pointer to member of class $C_i$ of type”, “array of $N_i$”, or “array of unknown bound of”.
If $P_i$ designates an array, the cv-qualifiers $cv_{i+1}$ on the element type are also taken as the cv-qualifiers $cv_i$ of the array.
[ Example: The type denoted by the type-id const int ** has three cv-decompositions, taking $U$ as “int”, as “pointer to const int”, and as “pointer to pointer to const int”. — end example ]
The n-tuple of cv-qualifiers after the first one in the longest cv-decomposition of T, that is, $cv_1$,$cv_2$,…,$cv_n$, is called the cv-qualification signature of T. - Two types T1 and T2 are similar__(類似)__ if they have cv-decompositions with the same $n$ such that corresponding $P_i$ components are either the same or one is “array of $N_i$” and the other is “array of unknown bound of”, and the types denoted by $U$ are the same.
上記のものを訳すと、おおよそ以下のようになると思われます。
-
型 T の cv 分解は、T が次のような $cv_i$ と $P_i$ のシーケンスです。
"$cv_0$ $P_0$ $cv_1$ $P_1$ $\cdots$ $cv_{n-1}$ $P_ {n-1}$ $cv_n$ U" $n\geq0$、
ここで、各 $cv_i$ は cv 修飾子のセットであり、各 $P_i$ は「ポインター」、「クラス型 $C_i$ のメンバーへのポインター」、「$N_i$ の配列」、または「不明な境界の配列」です。
$P_i$ が配列を指定する場合、要素型の cv 修飾子 $cv_{i + 1}$ も配列の cv 修飾子 $cv_i$ とみなされます。
[例:type-id がconst int **
で示される型には 3 つの cv 分解があり、U を「int
」、「const int
へのポインター」、および「const int
へのポインターへのポインター」に取ります。— 例を終了 ]
T の 最長 cv-decomposition を構成する先頭要素以降の cv 修飾(cv-qualifiers) の $n$ 個組(つまりトップレベルの cv 修飾を除いた cv 修飾のタプル)、つまり $cv_1$, $cv_2$, ..., $cv_n$ は、T の cv-qualification signature とよばれます。 -
2つの型 T1 と T2 は、次の場合__類似__しています。対応する $P_i$ コンポーネントが同じであるか、1 つが「$N_i$ の配列」で、もう 1 つが「不明な境界の配列」である同じ $n$ の cv 分解があり、U で示される型は同じです。
解説
まず、上記の例:
type-id が const int **
で示される型には 3 つの cv 分解があり、U を「int
」、「const int
へのポインター」、および「const int
へのポインターへのポインター」に取ります。
を解説してみます。const int ** 型、つまり "pointer to" "pointer to" const int 型の、U が int
のときの cv分解は、$n = 2$ で、 $cv_0$ は空、$P_0$ は *
(pointer to)、$cv_1$ は空、$P_1$は*
(pointer to)、$cv_2$ はconst
となりそうです。$P_n(ここでは P_2)$ は存在しないことに注意してください。
↓cv_0 ↓cv_1 ↓ cv_2
"pointer to" "pointer to" const int
↑P_0 ↑P_1 ↑U
また、U が const int
へのポインターのときは、$n = 1$ で、$cv_0$ は空、$P_0$ は *
(pointer to)、$cv_1$は空となりそうです:
↓cv_0 ↓cv_1
"pointer to" ("pointer to" const int)
↑P_0 ↑U
さらに、U がconst int
へのポインターのポインターのときは、$n = 0$、$cv_0$ は空となる、というようになるようです。
ここで、const int * const *
とint**
は類似した型のようなので、これを解説してみます。類似した型とは、$n$ が同じで、$P_i$ も同じ(*
の表すものが "pointer to" 同士もしくは配列同士)、U も同じもの、とのことですが、cv 分解は複数考えられそうなので、ここでは最も長い cv 分解を考えることとします。まず、const int * const *
型、つまり "pointer to" const
"pointer to" const int
の cv 分解は、n = 2
, U が int
で、$cv_0$ が空、$P_0$が *
(pointer to)、$cv_1$がconst
、$P_1$が *
(pointer to)、$cv_2$ がconst
、となりそうです。
↓cv_0 ↓cv_1 ↓cv_2
"pointer to" const "pointer to" const int
↑P_0 ↑P_1 ↑U
次に、int **
型、つまり "pointer to" "pointer to" int
の場合の最も長い cv 分解は、n = 2
, U が int
で、$cv_0$が空、$P_0$が *
(pointer to)、$cv_1$が空、$P_1$が *
(pointer to)、$cv_2$ は空となりそうです。
↓cv_0 ↓cv_1 ↓cv_2
"pointer to" "pointer to" int
↑P_0 ↑P_1 ↑U
上記 2 つの型の cv 分解を比較すると、$n$ は両方とも 2、U もともに int
、$P_0, P_1$はそれぞれともに "pointer to"となっているので、これは型の類似の条件を満たします。よって const int * const *
と int **
は類似の型のようです。
また、上記の型が類似である条件をよく見ると、大雑把には、*
の数と U の型が等しければ、cv 修飾のあるなしに関わらず、類似の型と言えそうです。これを考えると、冒頭に書いた「オブジェクト X の型と T それぞれで、全てのレベルの cv 修飾を除去した型が等しい」という条件は、型が類似である条件と同じものであると考えられそうです。
おわりに
cv 分解の構成方法は定義されていないため、上記の解説は C++ の気持ちで構成した場合のものになります...
また、間違い等ありましたら、コメント、編集リクエストなどを送っていただければ幸いです。