質問編集履歴

1

2019/03/15 06:48

投稿

tiitoi
tiitoi

スコア21956

test CHANGED
File without changes
test CHANGED
@@ -141,3 +141,269 @@
141
141
 
142
142
 
143
143
  すみませんが、よろしくおねがいします。
144
+
145
+
146
+
147
+ ## 回答いただいた内容に関する理解
148
+
149
+
150
+
151
+ alphya さんにご回答いただいた内容を自分の勉強用に整理しました。
152
+
153
+ もし誤った解釈があれば、ご指摘いただければ幸いです。
154
+
155
+
156
+
157
+ ----
158
+
159
+
160
+
161
+ * テンプレートの元々の意図は、型が異なり内容が同じクラスや関数を複数作る必要がないようにするため。(例: sqrt, sqrtf, sqrtl)
162
+
163
+ * 昔はテンプレート引数に非依存の識別子を先に名前解決することは、許可及び推奨されていたが、必須ではなかった。
164
+
165
+ * そのため、識別子の名前解決はテンプレートの定義の解析時には行わないで、テンプレートを実体化するまで名前解決を遅らせることができた。
166
+
167
+
168
+
169
+ ### 「two-phase name lookup」が必要になった理由
170
+
171
+
172
+
173
+ テンプレートの実体化時に名前解決を行う方法だと、テンプレートが前後の文脈に依存する状況がおき、バグを引き起こしやすくなる。
174
+
175
+
176
+
177
+ 例:
178
+
179
+ ```cpp
180
+
181
+ #include <cstdio>
182
+
183
+
184
+
185
+ void func(void*) { std::puts("The call resolves to void*") ;}
186
+
187
+
188
+
189
+ template<typename T> void g(T x)
190
+
191
+ {
192
+
193
+ func(0);
194
+
195
+ }
196
+
197
+
198
+
199
+ void func(int) { std::puts("The call resolves to int"); }
200
+
201
+
202
+
203
+ int main()
204
+
205
+ {
206
+
207
+ g(3.14);
208
+
209
+ }
210
+
211
+ ```
212
+
213
+
214
+
215
+ * two-phase name lookup を行う場合
216
+
217
+
218
+
219
+ テンプレートが定義された段階で `template<typename T> void g(T x)` の中身の解析が行われる。
220
+
221
+ この時点で `func(0)` にマッチするものは、`void func(void*)` だけなので、`void func(void*)` に名前解決される。
222
+
223
+
224
+
225
+ * two-phase name lookup を行わない場合
226
+
227
+
228
+
229
+ `g(3.14)` でテンプレートが実体化された時点で `template<typename T> void g(T x)` の中身の解析が行われる。
230
+
231
+ この時点で `func(0)` にマッチするものは、`void func(int)` と `void func(void*)` の2つがあるので、`void func(int)` に名前解決される。
232
+
233
+ もし、`void func(int)` がなかったならば、`void func(void*)` に名前解決される。
234
+
235
+ このように、テンプレートの後に `void func(int)` があるかどうかでテンプレートの意味が変わってしまう。 (前後の文脈に依存してしまう)
236
+
237
+
238
+
239
+ ### MSVC のバージョン 15.3 以前の実装
240
+
241
+
242
+
243
+ * テンプレートは宣言だけチェックして、定義の部分は記録だけしておき、実体化されるまで中身のチェックはしない。
244
+
245
+ * テンプレートの実体化が必要になった段階で、記録してあるリストからテンプレートの実装を探してきて、中身をチェックする。
246
+
247
+
248
+
249
+ #### 例1: テンプレートの中身は、実体化されるまでチェックされない。
250
+
251
+
252
+
253
+ ```
254
+
255
+ #include <cstdio>
256
+
257
+
258
+
259
+ template <typename T>
260
+
261
+ void hoge(T t) {
262
+
263
+ 1 // ; がない
264
+
265
+ }
266
+
267
+
268
+
269
+ int main()
270
+
271
+ {
272
+
273
+ }
274
+
275
+ ```
276
+
277
+
278
+
279
+ gcc: `source_file.cpp:5:6: error: expected ';' after expression`
280
+
281
+ vc++: コンパイルが通る。
282
+
283
+
284
+
285
+ #### 例2: テンプレートが定義された段階では曖昧さがあっても、実体化された段階で曖昧さがないケース
286
+
287
+
288
+
289
+ ```cpp
290
+
291
+ #include <cstdio>
292
+
293
+ #include <vector>
294
+
295
+
296
+
297
+ template<class C>
298
+
299
+ void f(const C &container) {
300
+
301
+ C::const_iterator *x;
302
+
303
+ }
304
+
305
+
306
+
307
+ int main()
308
+
309
+ {
310
+
311
+ std::vector<int> v;
312
+
313
+ f(v);
314
+
315
+ }
316
+
317
+ ```
318
+
319
+
320
+
321
+ テンプレートが実体化されていない段階では、`C::const_iterator` は型なのかクラス `C` で定義された static 定数なのかわからない。
322
+
323
+
324
+
325
+ * `C::const_iterator` が型と解釈した場合: `C::const_iterator *x;` は型 `C::const_iterator` のポインタ `*x`
326
+
327
+ * `C::const_iterator` が定数と解釈した場合: `C::const_iterator *x;` は定数 `C::const_iterator` と変数 `x` の乗算
328
+
329
+ `C::const_iterator` を型と認識させるには `typename C::const_iterator` と指定する必要がある。
330
+
331
+
332
+
333
+ ```cpp
334
+
335
+ #include <cstdio>
336
+
337
+ #include <vector>
338
+
339
+
340
+
341
+ template<class C>
342
+
343
+ void f(const C &container) {
344
+
345
+ C::const_iterator *x;
346
+
347
+ }
348
+
349
+
350
+
351
+ int main()
352
+
353
+ {
354
+
355
+ std::vector<int> v;
356
+
357
+ f(v);
358
+
359
+ }
360
+
361
+ ```
362
+
363
+
364
+
365
+ gcc:
366
+
367
+ ```
368
+
369
+ source_file.cpp: In function ‘void f(const C&)’:
370
+
371
+ source_file.cpp:6:22: error: ‘x’ was not declared in this scope
372
+
373
+ C::const_iterator *x;
374
+
375
+ ^
376
+
377
+ source_file.cpp: In instantiation of ‘void f(const C&) [with C = std::vector<int>]’:
378
+
379
+ source_file.cpp:12:8: required from here
380
+
381
+ source_file.cpp:6:21: error: dependent-name ‘C:: const_iterator’ is parsed as a non-type, but instantiation yields a type
382
+
383
+ C::const_iterator *x;
384
+
385
+ ^
386
+
387
+ source_file.cpp:6:21: note: say ‘typename C:: const_iterator’ if a type is meant
388
+
389
+ ```
390
+
391
+ vc++: コンパイルが通る。
392
+
393
+
394
+
395
+ two-phase name lookup を行う gcc は実体化前に解析するのでエラーとなるが、MSVC は実体化された段階で解析するので、この時点では `C::const_iterator` は型と判明しているのでエラーにならない。
396
+
397
+
398
+
399
+ ### 参考文献
400
+
401
+
402
+
403
+ - [Two phase name lookup for C++ templates - Why? - Stack Overflow](https://stackoverflow.com/questions/12561544/two-phase-name-lookup-for-c-templates-why)
404
+
405
+ - [What exactly is "broken" with Microsoft Visual C++'s two-phase template instantiation? - Stack Overflow](https://stackoverflow.com/questions/6273176/what-exactly-is-broken-with-microsoft-visual-cs-two-phase-template-instanti)
406
+
407
+ - [C++ Team Blog | Two-phase name lookup support comes to MSVC](https://devblogs.microsoft.com/cppblog/two-phase-name-lookup-support-comes-to-msvc/)
408
+
409
+ - [A Proposed New Template Compilation Model](http://www.open-std.org/JTC1/SC22/WG21/docs/papers/1996/N0906.pdf)