1-Intro.ipynb 20.2 KB
Newer Older
1
2
{
 "cells": [
3
4
  {
   "cell_type": "markdown",
5
   "metadata": {},
6
   "source": [
7
    "# [Getting started in C++](/) - [Templates](/notebooks/4-Templates/0-main.ipynb) - [Introduction](/notebooks/4-Templates/1-Intro.ipynb)"
8
9
   ]
  },
10
11
  {
   "cell_type": "markdown",
12
13
14
   "metadata": {
    "toc": true
   },
15
   "source": [
16
    "<h1>Table of contents<span class=\"tocSkip\"></span></h1>\n",
17
    "<div class=\"toc\"><ul class=\"toc-item\"><li><span><a href=\"#Motivation\" data-toc-modified-id=\"Motivation-1\">Motivation</a></span></li><li><span><a href=\"#Function-templates-(or-methods)\" data-toc-modified-id=\"Function-templates-(or-methods)-2\">Function templates (or methods)</a></span><ul class=\"toc-item\"><li><span><a href=\"#static_assert\" data-toc-modified-id=\"static_assert-2.1\"><code>static_assert</code></a></span></li></ul></li><li><span><a href=\"#Class-templates\" data-toc-modified-id=\"Class-templates-3\">Class templates</a></span><ul class=\"toc-item\"><li><span><a href=\"#Template-method-of-a-template-class\" data-toc-modified-id=\"Template-method-of-a-template-class-3.1\">Template method of a template class</a></span></li><li><span><a href=\"#Friendship-syntax\" data-toc-modified-id=\"Friendship-syntax-3.2\">Friendship syntax</a></span></li></ul></li><li><span><a href=\"#Type-or-non-type-template-parameter\" data-toc-modified-id=\"Type-or-non-type-template-parameter-4\">Type or non-type template parameter</a></span></li><li><span><a href=\"#Few-precisions-about-templates\" data-toc-modified-id=\"Few-precisions-about-templates-5\">Few precisions about templates</a></span></li></ul></div>"
18
19
20
21
22
23
24
25
26
27
28
29
30
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Motivation\n",
    "\n",
    "The typical introduction to the need of generic programming is implementing a function that determines the minimum between two quantities (by the way don't do that: STL provides it already...)\n"
   ]
  },
  {
   "cell_type": "code",
31
   "execution_count": null,
32
33
34
35
36
37
38
39
40
41
42
   "metadata": {},
   "outputs": [],
   "source": [
    "int min(int lhs, int rhs)\n",
    "{\n",
    "    return lhs < rhs ? lhs : rhs;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
43
   "execution_count": null,
44
45
46
47
48
49
50
51
52
53
54
   "metadata": {},
   "outputs": [],
   "source": [
    "double min(double lhs, double rhs)\n",
    "{\n",
    "    return lhs < rhs ? lhs : rhs;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
55
   "execution_count": null,
56
57
58
59
60
61
62
63
64
65
66
   "metadata": {},
   "outputs": [],
   "source": [
    "float min(float lhs, float rhs)\n",
    "{\n",
    "    return lhs < rhs ? lhs : rhs;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
67
   "execution_count": null,
68
   "metadata": {},
69
   "outputs": [],
70
71
72
73
74
75
76
77
78
79
80
81
82
83
   "source": [
    "#include <iostream>\n",
    "\n",
    "{\n",
    "    std::cout << min(5, -8) << std::endl;\n",
    "    std::cout << min(5., -8.) << std::endl;    \n",
    "    std::cout << min(5.f, -8.f) << std::endl;            \n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
84
    "Already tired? Yet we have still to define the same for unsigned versions, other types such as `short` ou `long`... And it's not very [**DRY** (Don't Repeat Yourself)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself): you may have noticed the implementation is exactly the same!\n",
85
    "\n",
86
    "## Function templates (or methods)\n",
87
88
89
90
91
92
    "\n",
    "A mechanism was therefore introduced in C++ to provide only one implementation: the **templates**:"
   ]
  },
  {
   "cell_type": "code",
93
   "execution_count": null,
94
95
96
97
98
99
100
101
102
103
104
105
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "T min2(T lhs, T rhs)\n",
    "{\n",
    "    return lhs < rhs ? lhs : rhs;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
106
   "execution_count": null,
107
   "metadata": {},
108
   "outputs": [],
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
   "source": [
    "#include <iostream>\n",
    "\n",
    "{\n",
    "    std::cout << min2<int>(5, -8) << std::endl;\n",
    "    std::cout << min2(5, -8) << std::endl;    \n",
    "    std::cout << min2(5., -8.) << std::endl;    \n",
    "    std::cout << min2(5.f, -8.f) << std::endl;            \n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see:\n",
    "\n",
    "* The type is replaced by a parameter (here called `T`)\n",
    "* In the function call, you might specify the type explicitly between brackets (`<>`). If not specified, the compiler may figure it out if said type is used for one of the parameters. In other words, for the following case it won't work:"
   ]
  },
  {
   "cell_type": "code",
132
   "execution_count": null,
133
134
135
136
137
138
139
140
141
142
143
144
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "T Convert(int value)\n",
    "{\n",
    "    return static_cast<T>(value);\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
145
   "execution_count": null,
146
   "metadata": {},
147
   "outputs": [],
148
149
   "source": [
    "{\n",
150
    "    Convert(5); // Error: can't figure out which type `T` to use!\n",
151
152
153
154
155
    "}"
   ]
  },
  {
   "cell_type": "code",
156
   "execution_count": null,
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
   "metadata": {},
   "outputs": [],
   "source": [
    "{\n",
    "    Convert<double>(5); // Ok\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You may have see noticed there are no constraints on `T` whatsoever. If you invoke a template and the implementation doesn't make sense for the chosen type the compiler will yell (no doubt you have already seen compilers are extremely talented to do so and it is even truer regarding templates...)"
   ]
  },
  {
   "cell_type": "code",
174
   "execution_count": null,
175
   "metadata": {},
176
   "outputs": [],
177
178
179
180
181
182
183
184
185
186
187
188
   "source": [
    "#include <string>\n",
    "\n",
    "{\n",
    "    Convert<std::string>(5); // Doesn't make sense so compiler will yell!\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
189
190
    "### `static_assert`\n",
    "\n",
191
    "Of course, it would be nicer to get a clearer error message when an impossible type is provided... C++ 20 should introduce the [concept](https://en.cppreference.com/w/cpp/language/constraints) to constraint properly which type is acceptable, but C++ 11 already introduced a way slightly better than C++ 03 with `static_assert`:"
192
193
194
195
   ]
  },
  {
   "cell_type": "code",
196
   "execution_count": null,
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
   "metadata": {},
   "outputs": [],
   "source": [
    "#include <type_traits> // for std::is_arithmetic\n",
    "\n",
    "template<class T>\n",
    "T Convert2(int value)\n",
    "{\n",
    "    static_assert(std::is_arithmetic<T>(), \"T must be an integer or a floating point!\");\n",
    "    return static_cast<T>(value);\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
212
213
214
   "execution_count": null,
   "metadata": {},
   "outputs": [],
215
216
217
218
   "source": [
    "#include <string>\n",
    "\n",
    "{\n",
219
220
    "    Convert2<std::string>(5); // Doesn't make sense so compiler will yell! \n",
    "                              // But first line is much clearer than previously...\n",
221
222
223
224
225
226
227
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
228
229
230
231
232
    "`static_assert` evolved in C++ 14:\n",
    "\n",
    "* In C++ 11, it takes two arguments: the test and a string which features an explanation on the topic at hand.\n",
    "* In C++ 14 and above, you might just give the test; it is actually handy when the test is already crystal clear:\n",
    "\n",
GILLES Sebastien's avatar
GILLES Sebastien committed
233
    "````static_assert(std::is_same<T, int>(), \"Check T is an integer\");```` is a tad overkill!\n",
234
    "\n",
235
236
237
    "That being said, if the test is not that trivial you should really use the possibility to add an explanation.\n",
    "\n",
    "\n",
238
239
240
241
242
243
244
    "## Class templates\n",
    "\n",
    "We have seen templates in the case of functions, but classes can be templated as well:"
   ]
  },
  {
   "cell_type": "code",
245
   "execution_count": null,
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "class HoldAValue\n",
    "{\n",
    "    public:\n",
    "        HoldAValue(T value);\n",
    "    \n",
    "        T GetValue() const;\n",
    "    \n",
    "    private:\n",
    "        T value_;    \n",
    "};"
   ]
  },
  {
   "cell_type": "code",
264
   "execution_count": null,
265
266
267
268
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
269
    "HoldAValue<T>::HoldAValue(T value) // the <T> is mandatory to identify properly the class\n",
270
271
272
273
274
275
    ": value_(value)\n",
    "{ }"
   ]
  },
  {
   "cell_type": "code",
276
   "execution_count": null,
277
278
279
280
281
282
283
284
285
286
287
288
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "T HoldAValue<T>::GetValue() const\n",
    "{ \n",
    "    return value_;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
289
290
291
   "execution_count": null,
   "metadata": {},
   "outputs": [],
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
   "source": [
    "#include <iostream>\n",
    "#include <string>\n",
    "\n",
    "{\n",
    "    HoldAValue integer(5);\n",
    "    std::cout << \"Integer hold: \" << integer.GetValue() << std::endl;\n",
    "\n",
    "    HoldAValue<std::string> string(\"Hello world!\"); // If type not specified explicitly it would have been char*...\n",
    "    std::cout << \"String hold: \" << string.GetValue() << std::endl;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The template must be reminded in the definition as well; please notice before the `::` the brackets with the parameters *without their type*.\n",
    "\n",
311
312
313
314
315
316
317
318
    "### Template method of a template class\n",
    "\n",
    "Notice a template class may provide template methods:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
319
   "execution_count": null,
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "class HoldAValue2\n",
    "{\n",
    "    public:\n",
    "        HoldAValue2(T value);\n",
    "    \n",
    "        T GetValue() const;\n",
    "    \n",
    "        template<class U>\n",
    "        U Convert() const;\n",
    "    \n",
    "    private:\n",
    "        T value_;  \n",
    "};"
   ]
  },
  {
   "cell_type": "code",
341
   "execution_count": null,
342
343
344
345
346
347
348
349
350
351
352
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "HoldAValue2<T>::HoldAValue2(T value)\n",
    ": value_(value)\n",
    "{ }"
   ]
  },
  {
   "cell_type": "code",
353
   "execution_count": null,
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "T HoldAValue2<T>::GetValue() const\n",
    "{ \n",
    "    return value_;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this case there are two `template` keyword for the definition: one for the class and the other for the method:"
   ]
  },
  {
   "cell_type": "code",
373
   "execution_count": null,
374
375
376
   "metadata": {},
   "outputs": [],
   "source": [
377
378
    "template<class T> // template type for the class first\n",
    "template<class U> // then template type for the method\n",
379
380
381
382
383
384
385
386
    "U HoldAValue2<T>::Convert() const\n",
    "{\n",
    "    return static_cast<U>(value_);\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
387
   "execution_count": null,
388
389
390
391
392
393
394
395
396
397
398
399
400
401
   "metadata": {},
   "outputs": [],
   "source": [
    "{\n",
    "    HoldAValue2 hold(9);\n",
    "    \n",
    "    hold.Convert<double>();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
402
    "### Friendship syntax\n",
403
    "\n",
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
    "There is a weird and specific syntax that is expected if you want to declare a friendship to a function. Quite naturally you would probably write something like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "class HoldAValue3\n",
    "{\n",
    "    public:\n",
    "    \n",
    "    HoldAValue3(T value);\n",
    "    \n",
    "    friend void print(const HoldAValue3<T>& obj);\n",
    "    \n",
    "    private:\n",
    "    \n",
    "    T value_;\n",
    "};"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "HoldAValue3<T>::HoldAValue3(T value)\n",
    ": value_(value)\n",
    "{ }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#include <iostream>\n",
447
    "\n",
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
    "template<class T>\n",
    "void print(const HoldAValue3<T>& obj)\n",
    "{\n",
    "    // Friendship required to access private data.\n",
    "    // I wouldn't recommend friendship where an accessor would do the same task easily!\n",
    "    std::cout << \"Underlying value is \" << obj.value_ << std::endl;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "{\n",
    "    HoldAValue3<int> hold(5);\n",
    "    print(hold); // LINK ERROR, and that is not something amiss in Xeus-cling!\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To make the friendship work, you have to use in the friendship declaration another label for the template parameter:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "class HoldAValue4\n",
    "{\n",
    "    public:\n",
    "    \n",
    "    HoldAValue4(T value);\n",
    "    \n",
    "    // 'Repeating' the list of template arguments and not using the ones from the class will fix the issue...\n",
    "    // T wouldn't have work here; the label MUST differ.\n",
    "    template<class U>\n",
    "    friend void print(const HoldAValue4<U>& obj);\n",
    "    \n",
    "    private:\n",
    "    \n",
    "    T value_;\n",
    "};"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class T>\n",
    "HoldAValue4<T>::HoldAValue4(T value)\n",
    ": value_(value)\n",
    "{ }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "// Notice it is only a label: in the definition I'm free to use the same label as for the class definitions!\n",
    "template<class T>\n",
    "void print(const HoldAValue4<T>& obj)\n",
    "{\n",
    "    std::cout << \"Underlying value is \" << obj.value_ << std::endl;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "{\n",
    "    HoldAValue4<int> hold(5);\n",
    "    print(hold); // Ok!\n",
    "}"
   ]
  },
540
541
542
543
544
545
546
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This way of declaring friendship works but is not entirely fullproof: `print<int>` is hence a friend of `HoldAValue4<double>`, which was not what was sought. Most of the time it's ok but there are 2 other ways to declare friendship; have a look at [this link](https://web.mst.edu/~nmjxv3/articles/templates.html) if you want to learn more about it."
   ]
  },
547
548
549
550
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
551
552
553
554
555
556
557
558
559
    "## Type or non-type template parameter\n",
    "\n",
    "The examples so far are using **type** parameters: the `T` in the example stands for a type and is deemed to be substituted by a type. Templates may also use **non type** parameters, which are in most cases `enum` or `integral constant` types (beware: floating point types parameters or `std::string` **can't** be used as template parameters!)\n",
    "\n",
    "Both can be mixed in a given template declaration:"
   ]
  },
  {
   "cell_type": "code",
560
   "execution_count": null,
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class TypeT, std::size_t Nelts>\n",
    "class MyArray\n",
    "{\n",
    "    public:\n",
    "        \n",
    "        explicit MyArray(TypeT initial_value);\n",
    "    \n",
    "    private:\n",
    "    \n",
    "        TypeT content_[Nelts];\n",
    "};"
   ]
  },
  {
   "cell_type": "code",
579
   "execution_count": null,
580
581
582
583
584
585
586
587
588
589
590
591
592
   "metadata": {},
   "outputs": [],
   "source": [
    "template<class TypeT, std::size_t Nelts>\n",
    "MyArray<TypeT, Nelts>::MyArray(TypeT initial_value)\n",
    "{\n",
    "    for (auto i = 0ul; i < Nelts; ++i)\n",
    "        content_[i] = initial_value;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
593
   "execution_count": null,
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
   "metadata": {},
   "outputs": [],
   "source": [
    "{\n",
    "    MyArray<int, 5ul> array1(2);\n",
    "    MyArray<double, 2ul> array2(3.3);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, you can't provide a type parameter where a non-type is expected (and vice-versa):"
   ]
  },
  {
   "cell_type": "code",
612
613
614
   "execution_count": null,
   "metadata": {},
   "outputs": [],
615
616
   "source": [
    "{\n",
617
    "    MyArray<5ul, 5ul> array1(2); // COMPILATION ERROR!\n",
618
619
620
621
622
    "}"
   ]
  },
  {
   "cell_type": "code",
623
624
625
   "execution_count": null,
   "metadata": {},
   "outputs": [],
626
627
   "source": [
    "{\n",
628
    "    MyArray<int, int> array1(2); // COMPILATION ERROR!\n",
629
630
631
632
633
634
635
636
637
638
639
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "## Few precisions about templates\n",
    "\n",
640
    "* Template parameters must be known or computed at **compile time**. You can't therefore instantiate a template from a value that was entered by the user of the program or computed at runtime.\n",
641
642
643
644
645
646
    "* In the template syntax, `class` might be replaced by `typename`:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
647
   "execution_count": null,
648
   "metadata": {},
649
   "outputs": [],
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
   "source": [
    "template<typename T>\n",
    "T Convert3(int value)\n",
    "{\n",
    "    return static_cast<T>(value);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are exactly zero differences between both keywords; some authors suggest conventions (e.g. use `typename` for POD types and `class` for the more complicated types) but they are just that: conventions!\n",
    "\n",
    "* The definition of the templates must be provided in header files, not in compiled files. The reason is that the compiler can't know all the types for which the template might be used: you may use `std::min` for your own defined types (provided they define a `<` operator...) and obviously STL writers can't foresee which type you will come up with!\n",
    "* The compiler will generate the code for each type given to the template; for instance if `Convert<double>`, `Convert<unsigned int>` and `Convert<short>` are used in your code, the content of this template function will be instantiated thrice! This can lead to **code bloat**: lots of assembler code is generated, and compilation time may increase significatively. \n",
    "* So templates are a _very_ nice tool, but make sure to use them only when they're relevant, and you may employ techniques to limit the amount of code actually defined within the template definitions.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
675
    "© _CNRS 2016_ - _Inria 2018-2019_   \n",
676
677
    "_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_  \n",
    "_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_"
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xeus-cling-cpp17"
  },
  "language_info": {
   "codemirror_mode": "text/x-c++src",
   "file_extension": ".cpp",
   "mimetype": "text/x-c++src",
   "name": "c++",
   "version": "-std=c++17"
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
  },
  "latex_envs": {
   "LaTeX_envs_menu_present": true,
   "autoclose": false,
   "autocomplete": true,
   "bibliofile": "biblio.bib",
   "cite_by": "apalike",
   "current_citInitial": 1,
   "eqLabelWithNumbers": true,
   "eqNumInitial": 1,
   "hotkeys": {
    "equation": "Ctrl-E",
    "itemize": "Ctrl-I"
   },
   "labels_anchors": false,
   "latex_user_defs": false,
   "report_style_numbering": false,
   "user_envs_cfg": false
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": true,
   "title_cell": "Table of contents",
   "title_sidebar": "Contents",
   "toc_cell": true,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": true
724
725
726
727
728
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}