From f58c8226b784cff4a6324f27286aff79340a17f5 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Wed, 27 Dec 2017 20:26:47 +0300 Subject: [PATCH 1/5] support asymptote --- Makefile | 2 ++ main.tex | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 3bc6a92..155e1b9 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ define clean_main_file rm -f ${MAIN_FILE_BASE}.aux $(MAIN_FILE_DEPS:%.tex=%.aux) rm -f ${MAIN_FILE_BASE}.log ${MAIN_FILE_BASE}.out rm -f ${MAIN_FILE_BASE}.pdf ${MAIN_FILE_BASE}.toc + rm -f ${MAIN_FILE_BASE}-*.{asy,pdf,pre,tex} rm -rf _minted-${MAIN_FILE_BASE} endef @@ -23,6 +24,7 @@ all: ${MAIN_FILE_BASE}.pdf ${HOW_TO_BASE}.pdf ${MAIN_FILE_BASE}.pdf: ${MAIN_FILE_BASE}.tex ${MAIN_FILE_DEPS} $(call clean_main_file) ${TEX_CMD} --interaction=nonstopmode --halt-on-error --shell-escape ${MAIN_FILE_BASE}.tex + find -name '${MAIN_FILE_BASE}-*.asy' -print0 | xargs -0 asy ${TEX_CMD} --interaction=nonstopmode --halt-on-error --shell-escape ${MAIN_FILE_BASE}.tex ${HOW_TO_BASE}.pdf: ${HOW_TO_BASE}.tex diff --git a/main.tex b/main.tex index c39c88e..3fe0a69 100644 --- a/main.tex +++ b/main.tex @@ -12,6 +12,8 @@ \usepackage{xcolor} \usepackage{hyperref} % for link +\usepackage[inline]{asymptote} + \definecolor{linkcolor}{HTML}{799B03} % цвет ссылок \definecolor{urlcolor}{HTML}{799B03} % цвет гиперссылок \hypersetup{colorlinks=true, linkcolor=linkcolor, urlcolor=urlcolor,} From c2a19c9dbab0efb4dbc0dcd0308f8ad4b3302237 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Fri, 29 Dec 2017 19:53:31 +0300 Subject: [PATCH 2/5] add type-deduction.tex --- Makefile | 2 +- main.tex | 1 + type-deduction.tex | 404 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 type-deduction.tex diff --git a/Makefile b/Makefile index 155e1b9..ff95587 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ MAIN_FILE_BASE := main -MAIN_FILE_DEPS := compilation.tex preprocessor.tex nullptr.tex rvalue-references.tex smart-pointers.tex +MAIN_FILE_DEPS := compilation.tex preprocessor.tex nullptr.tex rvalue-references.tex smart-pointers.tex type-deduction.tex HOW_TO_BASE := how-to-contribute TEX_CMD ?= pdflatex diff --git a/main.tex b/main.tex index 3fe0a69..b2c1559 100644 --- a/main.tex +++ b/main.tex @@ -47,5 +47,6 @@ \include{nullptr} \include{rvalue-references} \include{smart-pointers} +\include{type-deduction} \end{document} diff --git a/type-deduction.tex b/type-deduction.tex new file mode 100644 index 0000000..00ff9e0 --- /dev/null +++ b/type-deduction.tex @@ -0,0 +1,404 @@ + \section{Вывод типов} + До \textit{С++11} был один набор правил вывода типов - для шаблонов функций. В \textit{С++11} появились два новых, из-за появления \mintinline{c++}{auto} и \mintinline{c++}{decltype}. + Все более широкое применение выводов типов освобождает от нужды писать очевидные или излишние типы. Давайте разберем отдельно + + \subsection{Вывод типов шаблона} + В чем прелесть знания данного пункта? Он является базой для одной из самых привлекательных фич \textit{С++11} - \mintinline{c++}{auto}. + + Пусть в данном пункте у нас будут такие обозначения. + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(ParamType param) +// и будем вызывать f(expr) +\end{minted} + + \subsubsection{Указатели} + Давайте начнем с указателей. Пусть у нас есть такая функция: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T *a); +\end{minted} + + Вывод типов будет работать так - он берет тип expr без последнего указателя и подставляет его в T. В данном примере показаны простые случаи: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int x; +const int *px; + +f(&x) // T - int, ParamType - int * +f(px) // T - const int, ParamType - const int * +\end{minted} + + Теперь давайте рассмотрим более общий случай с указателями. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(map *m); + +map* x; +map, char*>* y; + +f(x) // T - int, U - float, ParamType - map* +f(y) // T - vector, U - char, ParamType - map, char*>* +\end{minted} + \subsubsection{Ссылки и значения} + Теперь поговорим про передачу по ссылке и значению. То есть такие примеры +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T a); + +template +void g(T &a); +\end{minted} + + Здесь используются такие правила + \begin{enumerate} + \item Убрать ссылочную часть исходного типа + \item Выполняется сопоставление исходного типа с типом аргумента для определения T. (при выводе игнорируются \mintinline{c++}{const} и \mintinline{c++}{volatile}) + \end{enumerate} + Показательным примером будет такой код +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T x) { + x = 10; +} + +template +void g(T &x) { + x = 10; +} + +void main() { + int a = 5; + int &ra = a; + f(ra); // T - int(убирается ссылка), следовательно после f(ra) a = 5 + g(ra); // T - int, но уже у типа аргумента есть &, следовательно тип аргумента - int& и после g(ra) a = 10 +} +\end{minted} + + \subsubsection{Function type normalizing} что это? + + \subsubsection{Универсальная ссылка} + Сразу возникает вопрос что такое универсальная ссылка. Универсальная ссылка - ссылка, которая обьявляется как \textit{rvalue} ссылка, но ведет себя иначе при передаче аргументов, являющихся \textit{lvalue}. + Для темплейтов это пишется так: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T&& param) +\end{minted} + + Для их вывода применяются такие правила: + \begin{enumerate} + \item Если expr представляет собой lvalue, то и T и тип аргумента будут lvalue ссылками. Интересно, что это единственный случай, когда T выводится, как ссылка. + \item Если expr является rvalue, то у типа изначального игнорируется ссылочная часть и подставляется в T оставшееся + \end{enumerate} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int x; +const int cx; +const int ℞ + +// Здесь x, cx, rx являются lvalue +f(x) // T - int&, ParamType - int& +f(cx) // T - const int&, ParamType - const int& +f(rx) // T - const int&, ParamType - const int& + +// 27 - rvalue +f(27) // T - int, ParamType - int&& +\end{minted} + + Почему происходит так, а не иначе? + По идее должно быть правило, которое сопоставляет как взаимодействуют ссылки при T и ParamType. Всего получается четыре случая(\& и \&, \& и \&\& и так далее) В С++ есть такой механизм: + \begin{itemize} + \item Если где-то(или в T или в ParamType) есть \& - на выходе будет lvalue. + \item Если у нас два \&, то на выходе будет rvalue. + \end{itemize} + Но не надо забывать, что тип может быть rvalue-ссылкой, само значение все равно будет lvalue, потому что оно имеет имя. Именно из-за этого возникает проблема прямой передачи значений(perfect forwarding), о которой будет речь далее. + + \subsubsection{Особенности вывода типов для шаблонов} + \begin{enumerate} + \item \textbf{Deduction может фэйлиться}. + + Давайте рассмотрим такой пример +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T, T*); + +int main() +{ + f(0, NULL); +} + \end{minted} + С одной стороны можно подумать что 0 - int, а NULL - нулевой указатель. Но NULL - 0, то есть int, поэтому этот код не скомпилируется с ошибкой "void f(T,T *): не удалось вывести аргумент шаблон для "T *" из "int"" + + \item \textbf{Non-deduced context} + + Non-deduced context рассматривается в коде ниже и обычно нужен для того, чтобы вывести специальный тип. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +struct not_deduced { + typedef T type; +}; + +/* Например мы хотим реализовать С-style добавление в вектор */ +template +void bad(std::vector x, T value = 1); +template +void good(std::vector x, typename not_deduced::type value = 1); + +std::vector> x; +bad(x, 1.2); // рассмотрим первый аргумент - + // входной тип std::vector> + // тип аргумента - std::vector, следовательно T = std::complex + + // для второго входной - double, тип аргумента T, следовательно T = double + // мы зафэйлились :( + +good(x, 1.2); // для первого тоже самое + // у второго есть зависимое поле следовательно он ничего не выводит. + // поэтому T = std::complex из первого вывода +\end{minted} + + В данном примере мы хотели, чтобы передаваемый элемент был того же типа, что и контейнер. И non-deduced контекст помог нам в этом. + \item \textbf{Что тут нужно?} + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + +template +void f(int); + +int const a; +f(a); +\end{minted} + + \item \textbf{Аргументы-массивы} + + Часто люди думают, что массив и указатель на начало это синонимы. Это правда во множестве случаев, так как массив преобразуется в указатель на его первый элемент. Это преобразование позволяет компилироваться такому коду: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +const char name[] = "Briggs" +const char *ptr_to_name = name; +\end{minted} + + Но что будет, если передать массив шаблону, принимающему параметр по значению? + Пусть мы передали \mintinline{c++}{f(name)} + + Какие варианты выводов могут быть для этого? + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(const char param[]); // это корректно +void f(const char *param); +\end{minted} + + Но так как массив рассматривается как указатель, для f(name) T будет \mintinline{c++}{const char *}. + Но давайте рассмотрим ParamType - \mintinline{c++}{T&}. И в таком случае T будет \mintinline{c++}{const char[]} и ParamType - \mintinline{c++}{const char (&)[]} + + Одним из забавных применений данной техники является вывод количества элементов, содержащихся в массиве: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +constexpr std::size_t arraySize(T (&)[N]) noexcept { + return N; +} +\end{minted} + Модификатор constexpr делает результат функции доступным во время компиляции и можно сделать например массивы для каждого ключа свое значение. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int keyVals[] = {1, 2, 3, 4, 5, 6, 7}; +std::array mappedVals; +\end{minted} + + \item \textbf{Аргументы-функции} + + Вместо кучи слов легче написать пример: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void someFunc(int); + +template +void f1(T param); + +template +void f2(T& param); + +f1(someFunc) // тип - void(*)(int) +f2(someFunc) // тип - void(&)(int) +\end{minted} +\end{enumerate} + + \subsection{Вывод типов для auto} + Если вы прочли вывод типов для шаблонов, вы знаете почти все, что следует знать о выводе типа auto, поскольку за одним любопытным исключеним типа auto представляется собой вывод типа шаблона. Как так? Существует прямая взаимосвязь между выводом типа щаблона и выводом типа auto. + + В шаблонах при вызове f компиляторы ипользуют expr для вывода типов T и ParamType. Когда переменная обьявлена с использованием ключевого слова auto, она играет роль T в шаблоне, а спецификатор типа переменной действует как ParamType. + Давайте рассмотрим примеры: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +auto x = 27; +const auto cx = x; +const auto& rx = x; +\end{minted} + + Для вывода типов данных штук компилятор действует так, как это бы были данные темплейты: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void func_for_x(T param); +func_for_x(27); + +template +void func_for_cx(const T param); +func_for_cx(x); + +template +void func_for_rx(const T& param); +func_for_cx(x); +\end{minted} + + Собственно для всего того, что обьявлено выше для templates все работает также для auto. + + Но чем же они отличаются? До C++11 для инициализации обьектов было всего 2 возможности: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int x = 27; +int x(27); +\end{minted} + В С++11 кроме старых вариантов появились новые варианты. + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int x3 = {27}; +int x4{27}; +\end{minted} + + Заменив слово int на auto первые две конструкции будут вести себя также, но вторые две будут вести себя иначе. Их типом будет \mintinline{c++}{std::initializer_list}. Поэтому такой код \mintinline{c++}{auto x5 = {1, 2, 3.0}} не будет компилироваться, так как не удается вывести тип T для \mintinline{c++}{std::initializer_list}. Так к чему же все это? Рассмотрим такой код: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +auto x = {1, 2, 3}; // тут все норм. Тип - std::initializer_list + +template +void f(T param); + +f({1, 2, 3}); // ошибка вывода типа для T +\end{minted} + + Однако если сделать ParamType - \mintinline{c++}{std::initializer_list}, то все будет работать. + + Таким образом, единственное реальное различие между выводом типа для \mintinline{c++}{auto} и выводом типа шаблона заключается в том, что \mintinline{c++}{auto} предполагает, что инициализатор в фигурных скобках представляет собой \mintinline{c++}{std::initializer_list}, в то врем я как вывод типа шаблона этого не делает. + + Для \textit{С++11} это все, но в \textit{С++14} auto немного распростронили.\textit{ С++14 }допускает применение \mintinline{c++}{auto} для указания того, что возвращаемый тип функции должен быть выведен, а кроме того, лямбда-выражения \textit{С++14} могут использовать \mintinline{c++}{auto} в обьявлениях параметров. Однако такие штуки используют вывод типа для шаблонов. То есть такой код работать не будет: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +auto createInitList() { +return {1, 2, 3} +} +\end{minted} + + \subsection{Использование auto} + Часто люди думают, что использование auto - избегание неинициализированных переменных, длинных обьявлений переменных, но этим auto не ограничивается. + \begin{enumerate} + \item \textbf{Часто лучше определять auto, чем явно обьявлять тип.} + + Давайте рассмотрим такую ситуацию: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +std::vector v; +... +unsigned sz = v.size(); +\end{minted} + Но \mintinline{c++}{v.size()} возвращает не \mintinline{c++}{unsigned}, а \mintinline{c++}{std::vector::size_type}. В 32-битной системе это 32битный тип, а в 64-битной - 64. Но \mintinline{c++}{unsigned} - всегда 32. + Отлично, это еще только начало + + Давайте рассмотрим следующий код: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +std::unordored_map m; +... +for (const std::pair& p : m) { +... +} +\end{minted} + Выглядит нормально, но на самом деле тут происходит копирование: в мапе тип хранения \mintinline{c++}{std::pair}, поэтому он ищет преобразование \mintinline{c++}{std::pair} в \mintinline{c++}{std::pair} и создается временный обьект. + + \item \textbf{Не надо использовать auto всегда} + + Сделаем такой код. Пусть у нас есть функция, которая возвращает \mintinline{c++}{std::vector} и нас интересует 5 бит, который мы передадим дальше +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +bool res = f()[4]; +g(res); +\end{minted} + Тут все нормально. А теперь давайте поменяем \mintinline{c++}{bool} на \mintinline{c++}{auto}, и теперь у нас все не будет работать:) + + \mintinline{c++}{operator[]} у \mintinline{c++}{vector} возвращает не \mintinline{c++}{bool&} а специальную штуку \mintinline{c++}{std::vector::reference}, которая указывает на конкретный бит. Она хранит по факут указатель и смещение. Но у нас \mintinline{c++}{vector} оказывается временным и при вызове g он будет уничтожен, собственно res будет висячим:) + \item Использовать \mintinline{c++}{for (auto &&e : v)} - зачем это нужно? + \end{enumerate} + + \subsection {Decltype} + Что такое \mintinline{c++}{decltype}? Это конструкция, которая для данного имени или выражения сообщает тип этого имени или выражения + + Начнем с типичных случаев, в которых нет никаких подводных камней. В отличие от \mintinline{c++}{auto} и \mintinline{c++}{template} - \mintinline{c++}{decltype} возвращает точный тип имени или выражения, которое вы передаете ему: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +const int i; // decltype(i) = const int +bool f(const Widget& w); // decltype(f) = bool(const Widget&) +vector v; // decltype(v) = vector +if (v[0] == 0) ... // decltype(v[0]) = int& +\end{minted} + + Основное применение \mintinline{c++}{decltype} в \textit{С++11} - обьявление шаблонов функций, в которых возвращаемый тип функции зависит от типов ее параметров. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +ReturnType f(As&& ... a) { + return g(forward(a)...); +} +\end{minted} + То есть нам надо вернуть что-то, что возвращает функция \textbf{g}. Логично бы было написать вместо ReturnType \mintinline{c++}{decltype(g(forward(a)...))}, но это не является правильным, потому что условно когда компилятор считывает \mintinline{c++}{decltype} он еще ничего не знает что такое \textbf{а}. + + Поэтому в \textit{С++11} был придуман новый синтаксис - завершающий возвращаемый тип. Он уже позволяет указывать в спецификации возвращаемого типа параметры функции. Вместо того, что было написано наверху можно написать так. + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +auto f(As&& ... a) -> decltype(g(forward(a)...)) { + return g(forward(a)...); +} +\end{minted} + + Многие могли подумать, что вместe c \mintinline{c++}{auto} тут подразумевается вывод типов, но в \textit{С++11} это не так. Оно просто означает, что возвращаемый тип будет написан после аргументов. В C++14 мы можем опустить заверщающий возвращаемый тип, оставляя только одно ведущее ключевое слово \mintinline{c++}{auto}. При таком обьявлении \mintinline{c++}{auto} означает, что имеет место \textit{вывод типа}, а точнее \textit{вывод типа шаблона} + + Это может сыграть плохую шутку в том случае, когда например возвращаемый тип представляет собой ссылку. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +auto authAndAccess(Container& c, Index i) { + authenticateUser(); + return c[i]; +} +\end{minted} + Получается, что он проигнорирует ссылку и такой код: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +std::deque d; +... +authAndAccess(d, 5) = 10; +\end{minted} + Не будет компилироваться. + + Чтобы заставить \mintinline{c++}{authAndAccess} работать так, как мы хотим(чтобы можно было писать код сверху), нам надо использовать для ее возвращаемого типа выдов типа \mintinline{c++}{decltype}, т.е. указать, что \mintinline{c++}{authAndAccess} должна возвращать в точности тот же тип, что и выражение \mintinline{c++}{c[i]}. Добрые люди, предвидя необходимость использования в некоторых случаях правила вывода типа decltype, сделали это возможным в \textit{С++14} с помощью спецификатора \mintinline{c++}{decltype(auto)}. + + В этом есть смысл: \mintinline{c++}{auto} указывает, что тип должен быть выведен, а \mintinline{c++}{decltype} говорит о том, что в процессе вывода следует использовать правила \mintinline{c++}{decltype}. \\\\ + Использование \mintinline{c++}{decltype(auto)} не ограничивается возвращаемым типом функции. Он может быть удобен для обьявления переменны, когда хотим применять правила вывода типа decltype к инициализирующему выражению: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +Widget w; +const Widget& cw = w; +auto myWidget1 = cw; // Widget +decltype(auto) myWidget2 = cw; // const Widget& +\end{minted} + Теперь давайте перейдем к подводным камням \mintinline{c++}{delctype}. В начале параграфа говорилось, что \mintinline{c++}{decltype} для данного имени или выражения сообщает тип этого \textbf{имени или выражения}. То есть есть разница между a и (a): +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int a; +decltype(a) // int +decltype((a)) // int& +\end{minted} + Во втором случае у нас получается не имя, а выражение за счет скобочек, а так как внутри скобочек находится \textit{lvalue}, поэтому тип возращаемый будет \textit{lvalue}. + + И это может чувствоваться на таком примере. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + decltype(auto) f1() { + int x = 0; + ... + return x; + } + + decltype(auto) f2() { + int x = 0; + ... + return (x); + } +\end{minted} + Вроде бы разница между функциями небольшая, но f2 является бомбой замедленного действия, поведение которого нельзя предсказать. Поэтому с \mintinline{c++}{decltype} надо быть предельно внимательным, потому что малейшая неточность может привести к плохим последствиям. From 21f9b48223d508916d9424251485e64bed8c88ff Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Fri, 29 Dec 2017 20:36:01 +0300 Subject: [PATCH 3/5] deduction review --- type-deduction.tex | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/type-deduction.tex b/type-deduction.tex index 00ff9e0..276d515 100644 --- a/type-deduction.tex +++ b/type-deduction.tex @@ -1,26 +1,29 @@ \section{Вывод типов} - До \textit{С++11} был один набор правил вывода типов - для шаблонов функций. В \textit{С++11} появились два новых, из-за появления \mintinline{c++}{auto} и \mintinline{c++}{decltype}. - Все более широкое применение выводов типов освобождает от нужды писать очевидные или излишние типы. Давайте разберем отдельно + До \textit{С++11} был один набор правил вывода типов --- для шаблонов функций. В \textit{С++11} появились два новых языковых механизма \mintinline{c++}{auto} и \mintinline{c++}{decltype} и вывод типов используемый для шаблонов функций был адаптирован для них. + Применение вывода типов избавляет от необходимости писать типы в тех местах, где они могут быть определены из контекста. \subsection{Вывод типов шаблона} - В чем прелесть знания данного пункта? Он является базой для одной из самых привлекательных фич \textit{С++11} - \mintinline{c++}{auto}. - - Пусть в данном пункте у нас будут такие обозначения. + Рассмотрим следующий фрагмент кода. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template -void f(ParamType param) -// и будем вызывать f(expr) +void f(ParamType param); + +f(expr); \end{minted} + В нём объявлена шаблонная функция f, и одна вызывается от выражения expr. В месте вызова тип параметра ParamType выводится на основе типа выражения expr. Рассмотрим как разные виды ParamType влияют на вывод типа T. \subsubsection{Указатели} - Давайте начнем с указателей. Пусть у нас есть такая функция: + +% TODO: может быть сделать этот раздел как вывод в общем случае +% обходим ParamType и decltype(expr) и спускаемся по типам? + Начнем с указателей. Пусть есть такая функция: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template void f(T *a); \end{minted} - Вывод типов будет работать так - он берет тип expr без последнего указателя и подставляет его в T. В данном примере показаны простые случаи: + Вывод типов работает следующим образом. Он берет тип expr, если тип expr не является указателем --- вывод завершается с ошибкой. Иначе T выводится как то, на что ссылается этот указатель. В данном примере показаны простые случаи: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int x; const int *px; @@ -129,7 +132,8 @@ С одной стороны можно подумать что 0 - int, а NULL - нулевой указатель. Но NULL - 0, то есть int, поэтому этот код не скомпилируется с ошибкой "void f(T,T *): не удалось вывести аргумент шаблон для "T *" из "int"" \item \textbf{Non-deduced context} - + % TODO: написать, что сквозь него выводить невозможно + % TODO: дальше его можно абузить Non-deduced context рассматривается в коде ниже и обычно нужен для того, чтобы вывести специальный тип. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template @@ -157,16 +161,6 @@ \end{minted} В данном примере мы хотели, чтобы передаваемый элемент был того же типа, что и контейнер. И non-deduced контекст помог нам в этом. - \item \textbf{Что тут нужно?} - -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} - -template -void f(int); - -int const a; -f(a); -\end{minted} \item \textbf{Аргументы-массивы} From 790192756257d144355111c161959336586c064e Mon Sep 17 00:00:00 2001 From: Evgeniy Feder Date: Sat, 30 Dec 2017 13:10:34 +0300 Subject: [PATCH 4/5] Add non-deduced context, change first part of templates and added for(auto&&..) --- type-deduction.tex | 91 +++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/type-deduction.tex b/type-deduction.tex index 276d515..bacad16 100644 --- a/type-deduction.tex +++ b/type-deduction.tex @@ -13,36 +13,47 @@ \end{minted} В нём объявлена шаблонная функция f, и одна вызывается от выражения expr. В месте вызова тип параметра ParamType выводится на основе типа выражения expr. Рассмотрим как разные виды ParamType влияют на вывод типа T. - \subsubsection{Указатели} - -% TODO: может быть сделать этот раздел как вывод в общем случае -% обходим ParamType и decltype(expr) и спускаемся по типам? - Начнем с указателей. Пусть есть такая функция: -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template -void f(T *a); -\end{minted} - - Вывод типов работает следующим образом. Он берет тип expr, если тип expr не является указателем --- вывод завершается с ошибкой. Иначе T выводится как то, на что ссылается этот указатель. В данном примере показаны простые случаи: -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -int x; -const int *px; - -f(&x) // T - int, ParamType - int * -f(px) // T - const int, ParamType - const int * -\end{minted} - - Теперь давайте рассмотрим более общий случай с указателями. + \subsubsection{Общий вид вывода} + Начнем с того, как это выглядит в общем случае: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template void f(map *m); -map* x; +map* x; map, char*>* y; +map z; -f(x) // T - int, U - float, ParamType - map* +f(x) // T - int, U - const float, ParamType - map* f(y) // T - vector, U - char, ParamType - map, char*>* +f(z) // ошибка \end{minted} + Рассмотрим \mintinline{c++}{f(x)}: + \begin {center} + \begin {tabular} { |c|c|c|c| } + \hline + Замечание & Действие & Исходный тип & ParamType \\ + \hline + & & \mintinline{c++}{map *} & \mintinline{c++}{map *} \\ + \hline + Снаружи у обоих типов указатель & сняли указатель & \mintinline{c++}{map} & \mintinline{c++}{map} \\ + \hline + Снаружи у обоих типов map & убрали map & \mintinline{c++}{int, const float *} & \mintinline{c++}{T, U*} \\ + \hline + Видно, что T это \mintinline{c++}{int} & \mintinline{c++}{T = int} & \mintinline{c++}{const float *} & \mintinline{c++}{U*} \\ + \hline + Снаружи у обоих типов указатель & сняли указатель & \mintinline{c++}{const float} & \mintinline{c++}{U} \\ + \hline + Видно, что U это \mintinline{c++}{const float} & \mintinline{c++}{U = const float} & & \\ + \hline + \end {tabular} + \end {center} + Таким образом у нас \mintinline{c++}{T = int}, а \mintinline{c++}{U = const float}, то есть общий алгоритм такой: + \begin {itemize} + \item Если мы дошли до \mintinline{c++}{T}, или \mintinline{c++}{T&}, то подставить тип по специальным правилам, которые будут в следующем пункте. + \item Если оба типа --- указатели, снять указатели и продолжаем дальше. + \item Если оба типа --- одинаковые \mintinline{c++}{temlate}-типы, то снимаем внешний тип и продолжаем дальше. + \item Если у нас несостыковка(например в f(z) второй тип у map \mintinline{c++}{int}, а мы хотим \mintinline{c++}{U*}), то ошибка вывода + \end {itemize} \subsubsection{Ссылки и значения} Теперь поговорим про передачу по ссылке и значению. То есть такие примеры \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -78,8 +89,6 @@ } \end{minted} - \subsubsection{Function type normalizing} что это? - \subsubsection{Универсальная ссылка} Сразу возникает вопрос что такое универсальная ссылка. Универсальная ссылка - ссылка, которая обьявляется как \textit{rvalue} ссылка, но ведет себя иначе при передаче аргументов, являющихся \textit{lvalue}. Для темплейтов это пишется так: @@ -132,15 +141,25 @@ С одной стороны можно подумать что 0 - int, а NULL - нулевой указатель. Но NULL - 0, то есть int, поэтому этот код не скомпилируется с ошибкой "void f(T,T *): не удалось вывести аргумент шаблон для "T *" из "int"" \item \textbf{Non-deduced context} - % TODO: написать, что сквозь него выводить невозможно - % TODO: дальше его можно абузить - Non-deduced context рассматривается в коде ниже и обычно нужен для того, чтобы вывести специальный тип. + + Что такое non-deduced context? Это тот тип, сквозь который невозможно выводить тип. Почему может не выводиться тип? Легче рассмотреть это на примере: + + Пусть у нас есть такая структура: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template -struct not_deduced { - typedef T type; -}; - +struct not_deduced { typedef T type; }; +\end{minted} + А теперь сделаем такие перегрузки. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template<> +struct not_deduced { typedef T double; }; +template<> +struct not_deduced {typedef T double; }; +\end{minted} + Получается, что для \mintinline{c++}{double} есть два варианта \mintinline{c++}{non_deduced} и из-за этого неоднозначно выводится тип для double. + + Одно из применений \textit{Non-deduced context} - вывести специальный тип для данного контекста. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} /* Например мы хотим реализовать С-style добавление в вектор */ template void bad(std::vector x, T value = 1); @@ -312,7 +331,13 @@ Тут все нормально. А теперь давайте поменяем \mintinline{c++}{bool} на \mintinline{c++}{auto}, и теперь у нас все не будет работать:) \mintinline{c++}{operator[]} у \mintinline{c++}{vector} возвращает не \mintinline{c++}{bool&} а специальную штуку \mintinline{c++}{std::vector::reference}, которая указывает на конкретный бит. Она хранит по факут указатель и смещение. Но у нас \mintinline{c++}{vector} оказывается временным и при вызове g он будет уничтожен, собственно res будет висячим:) - \item Использовать \mintinline{c++}{for (auto &&e : v)} - зачем это нужно? + + \item \textbf{Использовать \mintinline{c++}{for (auto &&e : v)}} + + Бывают такие контейнеры, которые возвращают \textit{rvalue}-обьекты(например контейнер, который генерирует последовательность целых чисел, которые не хранятся в памяти). И если написать \mintinline{c++}{for(const auto &e : v)}, то можно придумать пример в котором он не будет работать. А при универсальной реализации будет работать \textit{reference collapsing rule} и все будет разделяться на \textit{rvalue} и \textit{lvalue}. + + Стоит отметить, когда в e - \textit{rvalue}, то e продлевает жизнь возвращенному значению из контейнера. + \end{enumerate} \subsection {Decltype} @@ -395,4 +420,4 @@ return (x); } \end{minted} - Вроде бы разница между функциями небольшая, но f2 является бомбой замедленного действия, поведение которого нельзя предсказать. Поэтому с \mintinline{c++}{decltype} надо быть предельно внимательным, потому что малейшая неточность может привести к плохим последствиям. + Вроде бы разница между функциями небольшая, но f2 является бомбой замедленного действия, поведение которого нельзя предсказать. Поэтому с \mintinline{c++}{decltype} надо быть предельно внимательным, потому что малейшая неточность может привести к плохим последствиям. \ No newline at end of file From 26174c2a00b801cfed01182804dde33cbdfb178c Mon Sep 17 00:00:00 2001 From: Evgeniy Feder Date: Sat, 30 Dec 2017 17:23:58 +0300 Subject: [PATCH 5/5] Change deduction for universal reference and modified for(auto&&) using --- type-deduction.tex | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/type-deduction.tex b/type-deduction.tex index bacad16..91652f1 100644 --- a/type-deduction.tex +++ b/type-deduction.tex @@ -99,8 +99,14 @@ Для их вывода применяются такие правила: \begin{enumerate} - \item Если expr представляет собой lvalue, то и T и тип аргумента будут lvalue ссылками. Интересно, что это единственный случай, когда T выводится, как ссылка. - \item Если expr является rvalue, то у типа изначального игнорируется ссылочная часть и подставляется в T оставшееся + \item На место \textit{T} подставляется или \textit{lvalue} или \textit{rvalue} ссылка в зависимости от того какой обьект мы передали(\textit{lvalue} или \textit{rvalue}) + + \item Происходит \textit{сжатие ссылок} + \begin{itemize} + \item Если T является \textit{lvalue}, то на выходе будет \textit{lvalue}. + \item Если T является \textit{rvalue}, то на выходе будет \textit{rvalue}. + \end{itemize} + \end{enumerate} \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int x; @@ -108,21 +114,14 @@ const int ℞ // Здесь x, cx, rx являются lvalue -f(x) // T - int&, ParamType - int& -f(cx) // T - const int&, ParamType - const int& -f(rx) // T - const int&, ParamType - const int& +f(x) // T - int& -> void f(int& &¶m) -> void f(int ¶m) +f(cx) // T - const int& -> void f(const int& &¶m) -> void f(const int ¶m) +f(rx) // T - const int& -> void f(const int& &¶m) -> void f(const int ¶m) // 27 - rvalue -f(27) // T - int, ParamType - int&& -\end{minted} - - Почему происходит так, а не иначе? - По идее должно быть правило, которое сопоставляет как взаимодействуют ссылки при T и ParamType. Всего получается четыре случая(\& и \&, \& и \&\& и так далее) В С++ есть такой механизм: - \begin{itemize} - \item Если где-то(или в T или в ParamType) есть \& - на выходе будет lvalue. - \item Если у нас два \&, то на выходе будет rvalue. - \end{itemize} - Но не надо забывать, что тип может быть rvalue-ссылкой, само значение все равно будет lvalue, потому что оно имеет имя. Именно из-за этого возникает проблема прямой передачи значений(perfect forwarding), о которой будет речь далее. +f(27) // T - int&& -> void f(int&& &¶m) -> void f(int &¶m) +\end{minted} + Но не надо забывать, что тип может быть \textit{rvalue-ссылкой}, но само значение все равно будет \textit{lvalue}, потому что оно имеет имя. Именно из-за этого возникает проблема прямой передачи значений(perfect forwarding), о которой будет речь далее. \subsubsection{Особенности вывода типов для шаблонов} \begin{enumerate} @@ -156,7 +155,7 @@ template<> struct not_deduced {typedef T double; }; \end{minted} - Получается, что для \mintinline{c++}{double} есть два варианта \mintinline{c++}{non_deduced} и из-за этого неоднозначно выводится тип для double. + Получается, что для \mintinline{c++}{double} есть два варианта \mintinline{c++}{non_deduced} и из-за этого неоднозначно выводится тип для \mintinline{c++}{double}. Одно из применений \textit{Non-deduced context} - вывести специальный тип для данного контекста. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -334,9 +333,9 @@ \item \textbf{Использовать \mintinline{c++}{for (auto &&e : v)}} - Бывают такие контейнеры, которые возвращают \textit{rvalue}-обьекты(например контейнер, который генерирует последовательность целых чисел, которые не хранятся в памяти). И если написать \mintinline{c++}{for(const auto &e : v)}, то можно придумать пример в котором он не будет работать. А при универсальной реализации будет работать \textit{reference collapsing rule} и все будет разделяться на \textit{rvalue} и \textit{lvalue}. + Контейнеры в \textit{С++} могут возвращать три типа данных: const lvalue, lvalue, rvalue. И чтобы обеспечить работу всех трех случаев не достаточно написать \mintinline{c++}{for(const auto &e : v)}, как делают многие. Если еще \textit{const lvalue} и \textit{rvalue} сработает, то если мы захотим изменять элементы контейнера, мы не сможем этого делать. - Стоит отметить, когда в e - \textit{rvalue}, то e продлевает жизнь возвращенному значению из контейнера. + Если сделать \mintinline{c++}{for (auto &e : v)}, то мы не сможем работать с \textit{const lvalue} обьектами. Поэтому лучше писать \mintinline{c++}{for (auto &&e : v)}. Для него будет работать \textit{сжатие ссылок}, которое обеспечивает корректную работу во всех случаях. \end{enumerate} @@ -359,9 +358,9 @@ return g(forward(a)...); } \end{minted} - То есть нам надо вернуть что-то, что возвращает функция \textbf{g}. Логично бы было написать вместо ReturnType \mintinline{c++}{decltype(g(forward(a)...))}, но это не является правильным, потому что условно когда компилятор считывает \mintinline{c++}{decltype} он еще ничего не знает что такое \textbf{а}. + То есть нам надо вернуть такой тип, который возвращает функция \textbf{g}. Логично бы было написать \mintinline{c++}{decltype(g(forward(a)...))}, но такой код не является корректным, потому что когда компилятор считывает \mintinline{c++}{decltype} он еще ничего не знает об \textbf{а}. - Поэтому в \textit{С++11} был придуман новый синтаксис - завершающий возвращаемый тип. Он уже позволяет указывать в спецификации возвращаемого типа параметры функции. Вместо того, что было написано наверху можно написать так. + Поэтому в \textit{С++11} был придуман новый синтаксис - завершающий возвращаемый тип. Он уже позволяет указывать в спецификации возвращаемого типа параметры функции a. Вместо того, что было написано наверху можно написать так. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template