teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

3

m

2020/08/12 12:00

投稿

yumetodo
yumetodo

スコア5852

answer CHANGED
@@ -235,4 +235,66 @@
235
235
  言い出しておいてなんですが正直わからんです。自分はC++のイテレータに関してはKISSの法則を投げ捨てたほうがうまくいく気がします。比較系は先述の通り三方比較演算子のおかげで自動定義がある程度使えますが、その他は愚直に書くのがなんだかんだいいのかなと思っています。依存名か非依存名かでうっかり引っかかったりする心配もないですし。
236
236
 
237
237
  本件とは別の問題点からになりますが、これまでイテレータを作るときに継承するべきとされていた`std::iterator`はC++17でdeprecatedになりました。まあそんなこんなであんまり継承したくない心情です。
238
- [P0174R2 Deprecating Vestigial Library Parts in C++17](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0174r2.html)
238
+ [P0174R2 Deprecating Vestigial Library Parts in C++17](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0174r2.html)
239
+
240
+ ---
241
+
242
+ # 追記2
243
+
244
+ > 私の勝手な憶測なのですが
245
+ > もしかして
246
+ >
247
+ > 規格1:
248
+ > その規格で必要になる規格1
249
+ > その規格で必要になる規格2
250
+ >
251
+ > といった書き方がされているのでしょうか。
252
+
253
+
254
+ 違います。構文を定義する文法です。C++に限らず広く用いられる記法です。
255
+ まず定義したい構文の名称を書き、それを構成する構文や記号などの並びをその下に字下げして書きます。
256
+ 例えば識別子(identifier)の定義は
257
+ [https://timsong-cpp.github.io/cppwp/n4861/lex.name#nt:identifier](https://timsong-cpp.github.io/cppwp/n4861/lex.name#nt:identifier)
258
+ にあるように表されます。
259
+
260
+ ```
261
+ identifier:
262
+ identifier-nondigit
263
+ identifier identifier-nondigit
264
+ identifier digit
265
+ ```
266
+
267
+ これはつまり、`identifier`という構文は、その下の各行のいずれかのことである、という定義です。じゃあ`identifier-nondigit`ってなに?と思って調べるとこれは
268
+
269
+ ```
270
+ identifier-nondigit:
271
+ nondigit
272
+ universal-character-name
273
+ ```
274
+
275
+ のように定義されています。じゃあ`nondigit`は?というと
276
+
277
+ ```
278
+ nondigit: one of
279
+ a b c d e f g h i j k l m
280
+ n o p q r s t u v w x y z
281
+ A B C D E F G H I J K L M
282
+ N O P Q R S T U V W X Y Z _
283
+ ```
284
+
285
+ と定義されています。
286
+
287
+ ちょっと戻ってもう一度これを見てください。
288
+
289
+ ```
290
+ identifier:
291
+ identifier-nondigit
292
+ identifier identifier-nondigit
293
+ identifier digit
294
+ ```
295
+
296
+ `identifier identifier-nondigit`ってどういうこと?と思うかもしれません。なんで`identifier`の定義に`identifier`が出てくるねん!ってなりますよね。これはつまりは再帰的定義をしているわけです。
297
+
298
+ 例えば`a`は`nondigit`であり、`identifier-nondigit`でもあり、`identifier`でもあります。
299
+
300
+ では`ab`はどうでしょうか?まず`b`は同様にして`identifier-nondigit`であると言えます。`a`が`identifier`であることはすでに述べたとおりです。したがって`identifier identifier-nondigit`の定義に当てはまり、`ab`は`identifier`だと言えます(定義文はスペースで区切られていますが、このスペースは定義文に含まれる構文用語を識別しやすくするためのものであってスペースを入れろという意味ではありません。つまり`a b`ではなく`ab`ということです。定義にスペースを入れてほしい場合は明示的に`space`みたいな構文用語を記述するはずです)。

2

追記

2020/08/12 12:00

投稿

yumetodo
yumetodo

スコア5852

answer CHANGED
@@ -130,4 +130,109 @@
130
130
 
131
131
  [https://wandbox.org/permlink/IJJvIQtlluedZIRj](https://wandbox.org/permlink/IJJvIQtlluedZIRj)
132
132
 
133
- ちなみにC++20を使えるなら三方比較演算子を使うと楽になると思います。
133
+ ちなみにC++20を使えるなら三方比較演算子を使うと楽になると思います。
134
+
135
+ ---
136
+
137
+ # 追記
138
+
139
+ ## なぜ派生クラスのコンストラクタのメンバー初期化リスト(`mem-initializer-list`)で基底クラスのメンバー変数を初期化できないか
140
+
141
+ ### `mem-initializer-list`とは
142
+
143
+ まずはC++規格書の定義を見てみましょう。
144
+
145
+ > [class.base.init/1](https://timsong-cpp.github.io/cppwp/n4861/class.base.init#1)
146
+ >
147
+ > In the definition of a constructor for a class, initializers for direct and virtual base class subobjects and non-static data members can be specified by a ctor-initializer, which has the form
148
+ >
149
+ > ```
150
+ > ctor-initializer:
151
+ > : mem-initializer-list
152
+ >
153
+ > mem-initializer-list:
154
+ > mem-initializer ...opt
155
+ > mem-initializer-list , mem-initializer ...opt
156
+ >
157
+ > mem-initializer:
158
+ > mem-initializer-id ( expression-listopt )
159
+ > mem-initializer-id braced-init-list
160
+ >
161
+ > mem-initializer-id:
162
+ > class-or-decltype
163
+ > identifier
164
+ > ```
165
+
166
+ 英文は読まずともまあ十分わかりやすいかと思いますが、例を見ながら確認しましょう。
167
+
168
+ ```cpp
169
+ struct foo {
170
+ int bar;
171
+ int hoge;
172
+ foo() : bar(3), hoge(7) {}
173
+ };
174
+ ```
175
+
176
+ 上記で言う`bar(3), hoge(7)`が`mem-initializer-list`です。また`bar(3)`や`hoge(7)`が`mem-initializer`です。そして`bar`や`hoge`が`mem-initializer-id`です。
177
+
178
+ ### 有効な`mem-initializer-id`とは
179
+
180
+ またしても規格書を見ていきます。
181
+
182
+ > [class.base.init/2](https://timsong-cpp.github.io/cppwp/n4861/class.base.init#2)
183
+ >
184
+ > In a __mem-initializer-id__ an initial unqualified __identifier__ is looked up in the scope of the constructor's class and, if not found in that scope, it is looked up in the scope containing the constructor's definition.
185
+ [ Note: If the constructor's class contains a member with the same name as a direct or virtual base class of the class, a __mem-initializer-id__ naming the member or base class and composed of a single identifier refers to the class member.
186
+ A __mem-initializer-id__ for the hidden base class may be specified using a qualified name.
187
+ — end note
188
+ ]
189
+ **Unless the __mem-initializer-id__ names the constructor's class, a non-static data member of the constructor's class, or a direct or virtual base of that class, the __mem-initializer__ is ill-formed.**
190
+
191
+ とくに太字で強調したUnless以降が大事なのでまとめてみます。これによれば、有効な`mem-initializer-id`とは次の3つです。
192
+
193
+ - コンストラクタを書いているクラス自身の名前: 移譲コンストラクタのこと
194
+ - コンストラクタを書いているクラス自身の`static`指定されていないメンバー変数の名前
195
+ - 基底クラス(仮想基底クラスを含む)の名前
196
+
197
+ これ以外を指定した場合、ill-formedである、と書かれています。
198
+
199
+ これはどういうことかと言うと、メンバー初期化リストの時点では基底クラスは初期化されていないためアクセスできないということだと考えられます。
200
+
201
+ 以上の内容は`base class protected member init direct`でググって一番上に出てきた
202
+ [inheritance - Initialize parent's protected members with initialization list (C++) - Stack Overflow](https://stackoverflow.com/questions/2290733/initialize-parents-protected-members-with-initialization-list-c)
203
+ を元に書かれています。
204
+
205
+ ### 結論
206
+
207
+ 基底クラスのメンバー変数は有効な`mem-initializer-id`ではないのでill-formed
208
+
209
+ ## protectedメンバーを使うことは妥当か
210
+
211
+ > しかしそれでは各Iteratorにカウンタ操作を指定するoperatorを記載せねばならず
212
+ コードのサイズが増えてしまうように感じるのですが
213
+ それでも依存関係の簡略化を優先したほうが良いのでしょうか。
214
+
215
+ C++のイテレータを作るのがとっても面倒なのはものすごくよくわかります。
216
+
217
+ 実際にSTLの実装を覗いてみることにします。
218
+
219
+ ### msvcの実装
220
+
221
+ [https://github.com/microsoft/STL/blob/master/stl/inc/vector](https://github.com/microsoft/STL/blob/master/stl/inc/vector)
222
+ [https://github.com/microsoft/STL/blob/master/stl/inc/xmemory](https://github.com/microsoft/STL/blob/master/stl/inc/xmemory)
223
+
224
+ これは`_Vector_iterator`→`_Vector_const_iterator`→`_Iterator_base`という継承になっていて、operator類はそれぞれ定義しているのがわかります。
225
+
226
+ ### libstdc++の実装
227
+
228
+ [https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_vector.h](https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_vector.h)
229
+ [https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/bits/stl_iterator.h](https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/bits/stl_iterator.h)
230
+
231
+ イテレータに関しては極めてシンプルで、iteratorとconst_iteratorは同じ`__normal_iterator`クラスでできています。protectedメンバーがありますが、std::vectorから使う文についてはprotectedである必要を見いだせないので多分別の用途で使ってるのでしょう。
232
+
233
+ ### 結論
234
+
235
+ 言い出しておいてなんですが正直わからんです。自分はC++のイテレータに関してはKISSの法則を投げ捨てたほうがうまくいく気がします。比較系は先述の通り三方比較演算子のおかげで自動定義がある程度使えますが、その他は愚直に書くのがなんだかんだいいのかなと思っています。依存名か非依存名かでうっかり引っかかったりする心配もないですし。
236
+
237
+ 本件とは別の問題点からになりますが、これまでイテレータを作るときに継承するべきとされていた`std::iterator`はC++17でdeprecatedになりました。まあそんなこんなであんまり継承したくない心情です。
238
+ [P0174R2 Deprecating Vestigial Library Parts in C++17](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0174r2.html)

1

m

2020/08/11 15:54

投稿

yumetodo
yumetodo

スコア5852

answer CHANGED
@@ -126,4 +126,8 @@
126
126
 
127
127
  return 0;
128
128
  }
129
- ```
129
+ ```
130
+
131
+ [https://wandbox.org/permlink/IJJvIQtlluedZIRj](https://wandbox.org/permlink/IJJvIQtlluedZIRj)
132
+
133
+ ちなみにC++20を使えるなら三方比較演算子を使うと楽になると思います。