From 05103d69773271be8187446f1d1a9ff53a5bfbde Mon Sep 17 00:00:00 2001 From: DrDet Date: Wed, 27 Dec 2017 13:47:09 +0300 Subject: [PATCH 1/9] add metaprogramming notes(Vaksman Denis) --- main.tex | 3 +- metaprogramming.tex | 514 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 metaprogramming.tex diff --git a/main.tex b/main.tex index 8e24d4e..cf48c60 100644 --- a/main.tex +++ b/main.tex @@ -18,6 +18,7 @@ \usepackage{minted} % for highlight +\usepackage{multirow} % for merging rows in tables \voffset=-20mm \textheight=220mm @@ -44,5 +45,5 @@ \include{preprocessor} \include{nullptr} \include{rvalue-references} - +\include{metaprogramming} \end{document} diff --git a/metaprogramming.tex b/metaprogramming.tex new file mode 100644 index 0000000..d9d33e6 --- /dev/null +++ b/metaprogramming.tex @@ -0,0 +1,514 @@ +\section{Метопрограммирование в C++11 и выше} + \subsection{Expression SFINAE} + \subsubsection{Основная суть} +Одним из самых значительных улучшений SFINAE в C++11 стало появление так называемого Expression SFINAE. \\\\ +Его суть крайне проста. +Теперь в параметрах шаблона и в сигнатуре функции разрешено использовать любые выражения. +Невозможность вычисления трактуется как SFINAE-failure. \\ +До C++03 были разрешены только constant expressions. +В случае использования других выражений всегда возникал hard-error. \\\\ +Expression SFINAE дает нам огромный потенциал, позволяя практически полностью перейти от шаблонных метафункций к привычным выражениям. \\\\ +Весьма распространенным примером использования Expression SFINAE является использование его с инфиксным оператором запятая. +Он вычисляет свои аргументы и возвращает правый.\\\\ +Используя эту особенность, мы можем перечислить все выражения, которые должны быть вычислены для SFINAE проверок, например в возвращаемом типе, и последним указать то, которое используется по факту. +Благодаря правой ассоциативности этого оператора, все перечисленные проверки будут проведены, и результатом нашего перечисления будет являться фактический возвращаемый тип.\\\\ +Посмотрим, насколько проще становится код с использованием оператора запятая и Expression SFINAE на примере функции \texttt{void print(T)}: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +decltype(std::declval().cbegin(), std::declval().cend(), void()) +print(T container) +{ + std::cout << "Values:{ "; + for(auto value : container) + std::cout << value << " "; + std::cout << "}\n"; +} + +template +decltype(std::cout << std::declval(), void()) +print(T value) +{ + std::cout << "Value: " << value << "\n"; +} +\end{minted} +Эта функция печатает объект T или как контейнер, или как примитвный тип.\\\\ +Раньше для проверки, что T является контейнером, мы проверяли наличие у него типа \texttt{const\_iterator} с помощью метафункции \texttt{is\_const\_iterator}. +Для проверки, что T - примитвный тип использовалась метафункция \texttt{is\_integral} из \texttt{std::type\_traits}.\\\\ +Сейчас же мы можем попытаться \textquotedblright вызвать\textquotedblright\ функции cbegin и cend, возвращающие \texttt{const\_iterator}, прямо в возвращаемом типе функции. +Для проверки, что T может быть напечатан, как примитивный тип, мы можем напрямую написать выражение, передающее его в поток вывода. +Так мы достигаем абсолютной гарантии того, что он может быть напечатан. +\subsubsection{Предпосылки к нововведению} +В C++03 следующий код не компилировался, хотя стандарт того времени не запрещал подобное поведение: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template struct A {}; + +char xxx(int); +char xxx(float); + +template A f(T){} + +int main() +{ + f(1); +} +\end{minted} +Рассмотрим его подробнее: +\begin{enumerate} + \item Из контекста вызова f(1) успешно осуществляется вывод типа: \\ + T $\rightarrow$ int + \item Выведенный тип подставляется в нужные места: \\ + \texttt{A f(int)} + \item Компилятор анализирует эту строчку, и находит выражение, которое не является constant expression, компиляция прерывается: \\ + \texttt{xxx((int)0)} - не constant expression +\end{enumerate} + +Такое поведение было связано с технической сложностью реализации подстановки template аргументов в общем случае. +Она осуществлялась с помощью упрощенного метода семантической проверки. +Были специальные SFINAE правила, которые сильно отличались от обычной проверки выражений, вместо полной проверки семантики. Они были больше применимы именно для подстановки типов.\\\\ +В C++11 для решения этой проблемы было приянто решение разрешить вычисление обычных выражений при подстановке и большинство ошибок трактовать, как SFINAE-errors. \\ + +Был составлен список ошибок, которые не будут трактоваться как SFINAE-errors: +\begin{enumerate} +\item Ошибки, вызванные при исполнении чего-то внешнего для выражения, т.е. вычислении выражений в так называемом non-immediate context (инстанцирование шаблона, генерация определения неявно-объявленного конструктора копирования). +\item Ошибки, вызванные из-за несоблюдения + \href{https://gcc.gnu.org/onlinedocs/cpp/Implementation-limits.html}{\texttt{implementation\_limits}}. + \item Ошибки нарушения прав доступа. +\end{enumerate} +Все остальные ошибки было решено трактовать, как SFINAE-failures. +\subsection{Default template arguments in function templates \& SFINAE} +В С++11 появилась возможность устанавливать значения параметров шаблона функций по умолчанию. \\\\ +Вывод типов в шаблонах функций с аргументами по умолчанию производится следующим образом: +\begin{enumerate} + \item Компилятор пытается вывести тип всех параметров шаблона. + \item Если какой-то параметр вывести не удалось, то используется его значение по умолчанию, если таковое имеется. В противном случае тип параметра не может быть выведен - deduction failure. +\end{enumerate} +Небольшой пример для лучшего понимания: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T t = 0, U u = 0); + +char c; //any symbol + +void g() +{ + f(1, c); // f(1,c) + f(1); // f(1,0) + f(); // error: T cannot be deduced + f(); // f(0,0) + f(); // f(0,0) +} +\end{minted} +Это нововведение можно использовать в SFINAE. Теперь мы можем писать SFINAE проверки прямо в аргументах шаблона функции по умолчанию, нисколько не меняя при этом сигнатуру функции (Шаблонная шапка не входит в сигнатуру). \\\\ +Рассмотрим в качестве примера реализацию функции модуля для знаковых чисел: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template ::value>>::type * = nullptr> +T abs(T val); +\end{minted} +Мы создаем безымянный шаблонный параметр, чтобы его невозможно было вывести из контекста вызова, и присваиваем ему значение по умолчанию, являющееся SFINAE проверкой. \\\\ +Достоиннством этого метода определенно является то, что SFINAE проверка осуществляется на более ранней стадии - при \textit{выводе} типа соответствующего параметра. +Это эффективно в смысле временных затрат на компиляцию.\\\\ +Но у этого метода есть недостаток. Так как шаблонная шапка не входит в сигнатуру функции, мы не можем аналогично написать версию функции для другого случая. +В нашем примере мы не можем добавить еще одну перегрузку для модуля от беззнаковых чисел: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template ::value>>::type * = nullptr> +T abs(T val) +{ + return -val; +} + +template ::value>>::type * = nullptr> +T abs(T val) +{ + return val; +} +\end{minted} +Данный код некорректен, так как функции имеют абсолютно одинаковую сигнатуру - нарушение one definition rule. +\subsection{constexpr} +Еще одним весьма полезным нововведением, делающим метапрогарммирование проще, является спецификатор \textbf{constexpr}. Он способствует еще большему перенесению вычислений некоторых выражений в compile-time. \\\\ +Для начала попробуем понять, что такое constant expression. \\ +\textbf{\textit{Constant expressions}} - это такие выражения, +которые могут быть использованы для указания размера статического массива или шаблонных аргументов, которые не являются типом. +Их значение известно на момент компиляции.\\ +Пример: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int n = 1; +std::array a1; // error: n is not a constant expression +int arr[n]; // error: n is not a constant expression +const int cn = 2; +std::array a2; // OK: cn is a constant expression +int arr[cn]; // OK: cn is a constant expression +\end{minted} +Сonstexpr гарантирует возможность использования переменных или функций в местах, где требуется \textit{constant expression}.\\\\ +Рассмотрим отдельно значение constexpr для переменных и для функций. +\subsubsection{Constexpr переменные} +Переменные, объявленные как \textbf{constexpr}, должны удовлетворять следующим требованиям: +\begin{enumerate} +\item Должны быть проинициализирована сразу при объявлении. +\item Выражение в инициализации должно быть \textit{constant expression}. +\end{enumerate} +\textbf{Constexpr} переменная по смыслу включает себя значение \textbf{const} переменной, но не наоборот. +Вообще говоря, не все \textbf{const} переменные являются \textit{constant expressions}.\\\\ +Отличие \textbf{constexpr} переменных от \textbf{const} в том, что \textbf{const} гарантирует лишь то, что значение переменной не будет изменено. Оно может быть неизвестно на момент компиляции.\\\\ +Пример: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int f(int x) +{ + if (x > 0) + return 10; + else + return 5; +} + +int four() +{ + return 4; +} + +const int m = four(); +int a[m]; // ошибка компиляции: m - не constant expression + +int main() +{ + int custom_v; + std::cin >> custom_v; + const int N = f(custom_v); + int b[N]; // ошибка компиляции: N - не constant expression + return 0; +} +\end{minted} +\texttt{N} никак не может быть известно на момент компиляции, несмотря на то, что является \textbf{const}. +Более того, \texttt{m} несмотря на свою логическую константность, не является \textit{constant expression}. \\\\ +Const переменная является \textit{constant expression} только если: +\begin{enumerate} + \item Она является интегральным типом или перечислением. (см. таблицу \ref{tab:types} категорий типов) + \item Выражение, которым инициализируется начальное значение - \textit{constant expression}. +\end{enumerate} +Пример: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int main() +{ + const int N = 3; + int numbers[N] = {1, 2, 3}; // N is constant expression + return 0; +} +\end{minted} +В данном случае N удовлетворяет тербованиям \textit{constant expression}. +\subsubsection{Constexpr функции} +Довольно полезная вещь, не имеющая аналогов до C++11. \\ +Объявление функции как \textbf{constexpr} гарантирует, что она является \textit{constant expression} и, как следствие, ее значение может быть посчитано на этапе компиляции. \\\\ +На \textbf{constexpr} функции накладываюстя довольно серьзеные требования: +\begin{enumerate} +\item Функция должна возвращать значение, являющееся \textbf{constexpr}. +\item Аргументы функции должны быть \textbf{constexpr}. +\item Функция может содержать следующие конструкции: \texttt{typedef}, различные вариации \texttt{using}, \texttt{static\_assert}, пустое выражение(;) и, собственно, \texttt{return}. +\item Функция может содержать не более одного \texttt{return}. +\end{enumerate} +И дополнительно для \textbf{constexpr} функций-членов: +\begin{enumerate} +\item Метод не может быть виртуальным. +\item Метод может быть объявлен с \texttt{=default} или c \texttt{=delete}. +\item \textbf{constexpr} метод является \textbf{const}, по умолчанию. +\end{enumerate} +Возможна рекурсия. \\\\ +То есть, основная суть \textbf{constexpr} функции должна содержаться в единственном return statement. +Из-за таких ограничений в \textbf{constexpr} функциях повсеместно используют тернарный оператор, +что делает стиль написания похожим на написание программ на функциональном языке программирования. \\\\ +Constexpr функции могут быть использованы для метапрограммирования в дополнение к привычным шаблонам. +Пример: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +constexpr bool isBaseOf() +{ + return std::is_base_of::value; +} +\end{minted} +Теперь, чтобы проверить является ли один класс базой для другого, мы можем просто вызвать функцию: \\ +\texttt{isBaseOf()} \\ +Это значительно улучшает код, делая его гораздо проще для понимания и читабельнее. +\subsection{if constexpr} +В C++17 появилась конструкция \textbf{if constexpr}. \\\\ +Позволяет, используя прямой синтаксис, создавать compile-time условие: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +if constexpr(/*condition*/) +{ + /*...*/ +} else /../ +\end{minted} +condition должно являться \textit{constant expression} типа bool. \\\\ +В отличии от обычного if statement-a не треует корректности(well-formed) выражений в false ветке. \\\\ +Рассмотрим как пример функцию модуля знаковых/беззнаковых чисел с использование if constexpr: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +T abs(T value) { + if constexpr(is_signed::value) { + return (value < 0 ? -value : value); + } else { + reutrn value; + } +} +\end{minted} +Как мы можем видеть, код по синтаксису не отличается от run-time версии с обычным условием. Никаких двух специализаций шаблона функции. \\\\ +Казалось бы, такое мощное средство позволяет нам полностью избавиться от необходимости +использования косвенных SFINAE проверок, использующих шаблоны. Но в действительности, это не так. +If constexpr не может в полной мере заменить SFINAE. \\\\ +Представим, что мы хотим сделать compile-time условие, в зависимости от которого перегрузки от некоторых типов, назовем их \textquotedblright плохими\textquotedblright, не будут генерироваться в коде вовсе. +С помощью SFINAE мы с легкостью можем сделать это так: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +enable_if::value>::type foo(T) { + /*...body...*/ +} +\end{minted} +Вызов \texttt{foo(Goo\_Type)} будет совершен успешно, вызов \texttt{foo(Bad\_Type)} приводит к ошибке компиляции. +Все работает так, как мы и хотели. \\\\ +Теперь попробуем переписать с использованием \textbf{if constexpr} вместо SFINAE: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void foo(T x) +{ + if constexpr(is_good::value) + { + /*...body...*/ + } + // cant write something to discard call from bad_type +} +\end{minted} +Заметим, что во втором случае вызов \texttt{foo(Bad\_Type)} - будет корректным и не приведет к ошибке компиляции. \\ +Дело в том, что в версии с \textbf{if constexpr} поверка осуществляется на более поздней стадии - на стадии, когда мы уже внутри тела функции, когда код уже сгенерирован. +Мы не имеем средств, чтобы \textquotedblright подавить\textquotedblright\ вызов, будучи внутри функции. +\subsection{Static assertions} +Предоставляет возможность писать compile-time asserts. \\\\ +Синтаксис: \\ +\texttt{static\_assert(condition, message)} \\\\ +где \texttt{condition} - это \textit{constant expression} типа bool, \texttt{message} - строковый литерал, сообщение выдаваемое при срабатывании ассерта. \\\\ +В случае condition $=$ false, происходит ошибка компиляции, печатается сообщение message, иначе ничего не происходит. \\\\ +Можно рассматривать \textbf{static assertions} как альтернативу SFINAE, аналогично \textbf{if constexpr}, рассмотренному ранее. \\ +Модернизируем предыдущий пример, используя static assertion: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void foo(T x) +{ + if constexpr(is_good::value) { + /*...body...*/ + } else { + static_assert(false, "No function foo() for Bad Types") + } +} +\end{minted} +Теперь при вызове \texttt{foo(Bad\_Type)} действительно имеем ошибку компиляции, но это, опять же, не то, что нужно. \\ +Перегрузка от плохого типа на самом деле есть, но при входе в нее происходит ошибка компиляции. Это не то же самое, что перегрузки нет. \\ +К примеру, SFINAE проверка на наличие перегрузки от плохого типа в нашем случае будет возвращать \texttt{true}, что противоречит тому, что мы хотели. +\subsection{Template variables} +В С++14 появилась возможность создавать шаблонные переменные. +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +constexpr T pi = T(3.14159265); + +float x = pi; +float y = pi; +\end{minted} +Шаблонные переменные обязаны быть \textbf{constexpr}. Допускаются специализациии. \\\\ +Наиболее часто применяются для упрощения метафункций из \texttt{std::type\_traits}. \\ +Рассмотрим на примере \texttt{is\_same}: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +constexpr bool is_same_v = is_same::value; +\end{minted} +Теперь вместо \texttt{is\_same::value} можем писать просто \texttt{is\_same\_v}. \\\\ +В C++14 все метафункции, возвращающие значение, имеют соответствующие шаблонные переменные с суффиксом \texttt{\_v}. +\subsection{Type alias \& Alias template} +\textbf{Type alias} - это псевдоним типа, объявленного ранее(подобно \texttt{typedef}). \textbf{Template alias} - псевдоним для шаблона.\\\\ +\textbf{Type alias} можно использовать, как альтернативу \texttt{typedef}: \\\\ +\texttt{using mytype = int;} \\\\ +\textbf{Alias template} позволяет написать собственный псевдоним с шаблонным параметром, например: \\ +\texttt{vector< T, mmap\_allocator > $\longmapsto$ my\_vector} \\ +Синтаксис: +\begin{minted} [frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +using my_vector = vector>; +\end{minted} +Как и \textbf{template variables}, \textbf{alias templates} используются для упрощения \texttt{std::type\_traits}. \\\\ +В C++14 все метафункции, возвращающие тип, имеют соответсвующий \textbf{alias template} с суффиксом \texttt{\_t} (\texttt{enable\_if\_t}).\\ +Позволяют сильно упростить код, так как пропадает необходимость писать \texttt{typename} и \texttt{::type}. +\subsection{Явное инстанцирование шаблонов. Подавление инстанцирования.} +Явное инстанцирование шаблона позволяет сгенерировать код шаблонной функции или класса для конкретных параметров без использования его в коде. \\\\ +Синтаксис: +\begin{minted} [frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +class my_class {} + +template +V f(T, U) {} + +template class my_class; // for classes +template double f(int, char); // for functions +\end{minted} +При таком инстанцировании инстанцируются все не-template мемберы. \\\\ + +В C++11 появилась возможность подавления неявного инстанцирования шаблона. \\\\ +Синтаксис: +\begin{minted} [frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +extern template class my_class; // for classes +extern template double f(int, char); // for functions +\end{minted} +Можно использовать это для уменьшения количества генерируемого кода. Например, можно подавить инстанцирование в header-файле и явно проинстанцировать в одном cpp-файле. \\ +Так оптимизируется время компиляции. +\section{std::type\_traits} +\texttt{std::type\_traits} - это библиотека, которая содержит множество полезных метафункции для работы с классами. В этой части будет проведен её обзор. \\\\ +\subsection{Вспомогательные типы} +\begin{itemize} + \item \texttt{\textbf{integral\_constant} \\\\ + Создает константу времени компиляции типа T со значением v. + \item \texttt{\textbf{true\_type}} = integral\_constant + \item \texttt{\textbf{false\_type}} = integral\_constant +\end{itemize} +Довольно удобно наследоваться от последних двух при написании своих метафункций: +\begin{minted} [frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +struct is_same : std::false_type {}; + +template +struct is_same : std::true_type {}; +\end{minted} +\subsection{Проверка на принадлежость типа к определенной категории} +\begin{table}[H] + \caption{\label{tab:types} Категории типов} + \begin{center} +\begin{tabular}{|c|c|c|c|c|} + \hline + & \textbf{primary categories} & \multicolumn{3}{|c|}{\textbf{composite categories}} \\ + \hline + \multirow{4}{*}{\textbf{fundamental}} & void & & & \\ + \cline{2-5} + & std::nullptr\_t & & \multirow{7}{*}{scalar} & \multirow{10}{*}{object} \\ + \cline{2-3} + & integral & \multirow{2}{*}{arithmetic} & & \\ + \cline{2-2} + & floating\_point & & & \\ + \cline{1-3} + \multirow{10}{*}{\textbf{compound}}& pointer & & & \\ + \cline{2-3} + & member\_object\_pointer & \multirow{2}{*}{member\_pointer} & & \\ + \cline{2-2} + & member\_function\_pointer & & & \\ + \cline{2-3} + & enum & & & \\ + \cline{2-4} + & union & & & \\ + \cline{2-4} + & class* & & & \\ + \cline{2-4} + & array & & & \\ + \cline{2-5} + & l\_value\_reference & \multirow{2}{*}{reference} & & \\ + \cline{2-2} \cline{4-5} + & r\_value\_reference & & & \\ + \cline{2-5} + & function & & & \\ + \cline{1-5} + \multicolumn{5}{|c|}{* = excluding unions} \\ + \hline +\end{tabular} +\end{center} +\end{table} +К интегральному типу относятся следующе встроенные типы:\\\\ +\texttt{bool, char, char16\_t, char32\_t, wchar\_t, short, int, long, long long}. \\\\ +\textbf{type\_traits} предоставляет набор метафункций, позволяющих проверить, принадлежит ли тип T к какой-либо категории из приведенных в таблице выше. \\\\ +Название таких метафункций имеет вид: \texttt{is\_/*название\_категории*/}. +\subsection{Проерка типа на наличие определенных свойств} +В C++11 поддерживается широкий набор метафункций для проверки наличия у типа определенных свойств. Рассмотрим основные из них: +\begin{itemize} +\item \textbf{\texttt{is\_const}} \\\\ +Проверяет наличие спецификатора const или const volaitile. +\item \textbf{\texttt{is\_volatile}} \\\\ +Проверяет наличие спецификатора volaitile или const volaitile. +\item \textbf{\texttt{is\_signed}} \\\\ +Проверяет, что если тип является арифметическим, то выражение T(-1) < T(0) = true. +То есть тип - знаковый. +\item \textbf{\texttt{is\_unsigned}} \\\\ +Проверяет, что если тип является арифметическим, то выражение T(0) < T(-1) = true. +То есть тип - беззнаковый. +\item \texttt{\textbf{is\_trivially\_copyable}} \\\\ +Проверяет, что тип - \textit{тривиально копируемый}. \\\\ +Тип считается \textit{тривиально копируемым}, если: +\begin{enumerate} +\item Любой его конструктор копирования, move конструктор, копирующий оператор присваивания, +move оператор присваивания является \textit{тривиальным} или помечен как deleted. +\item Хотя бы один из перечисленных не помечен как deleted. +\item Имеет \textit{тривиальный} деструктор, не помеченный как deleted. +\end{enumerate} +Все типы, совместимые с C, тривиально копируемы. \\\\ +Конструктор копирования, move конструктор, копирующий оператор присваивания, +move оператор присваивания (далее - операция) в классе T называется \textit{тривиальным}, если: +\begin{enumerate} +\item Он не определен пользователем (определен неявно, или помечен как default). +\item T не имеет виртуальных функций. +\item T не имеет вирутальных баз. +\item Соответсвующая операция, выбирающаяся для прямой базы T, тривиальна. +\item Соответсвующая операция, выбирающаяся для каждого не статического класса-мембера, тривиальна. +\end{enumerate} +Конструктор/деструктор называется \textbf{\textit{тривиальным}}, если: +\begin{enumerate} + \item Он не определен пользователем (определен неявно или помечен как default). + \item Он не виртуальный. + \item Все прямые базы класса имеют тривиальный конструктор/деструктор. + \item Все не статические мемберы класса имеют тривиальный конструктор/деструктор. +\end{enumerate} +За счет наложенных ограничений тривиально копируемый тип может копироваться побайтово. Например, с помощью \texttt{std::memcpy}. +\item \textbf{\texttt{is\_trivial}} \\\\ +Тип считается тривиальным, если он: +\begin{enumerate} +\item Тривиально копируемый. +\item Имеет хотя бы 1 конструктор по умолчанию, все они \textbf{\textit{тривиальны}} или помечены как deleted, +и хотя бы 1 из них не помечен как deleted. +\end{enumerate} +\item \texttt{\textbf{is\_constructible}} $(1)$\\ + \texttt{\textbf{is\_trivially\_constructible}} $(2)$\\ + \texttt{\textbf{is\_nothrow\_constructible}} $(3)$\\\\ +Имеет аргументы: template +\begin{enumerate} +\item Проверяет, что выражение \texttt{T obj(std::declval()}, вызывающее констурктор от типа корректно. +\item Проверяет (1) и что вызов конструктора не вызвыает \textit{не тривиальных} операций. +\item Проверяет (1) и что вызов конструктора noexcept. +\end{enumerate} +Есть аналогичные метафункции, проверяющие \texttt{default\_constructible, copy\_constructible, move\_constructible}. Они выражаются через соответствующие версии \texttt{is\_constructible}. +\textbf{\texttt{\item is\_assignable}} $(1)$ \\ +\textbf{\texttt{is\_trivially\_assignable}} $(2)$ \\ +\textbf{\texttt{is\_nothrow\_assignable}} $(3)$ \\\\ +Имеет аргументы: template +\begin{enumerate} +\item Проверяет, что выражение \texttt{std::declval() = std::declval()}, вызывающее оператор присваивания корректно. +\item Проверяет (1) и что вызов конструктора не вызвыает \textit{не тривиальных} операций. +\item Проверяет (1) и что вызов конструктора noexcept +\end{enumerate} +Есть аналогичные метафункции, проверяющие \texttt{copy\_assignable, move\_assignable}. Они выражаются через соответствующие версии \texttt{is\_assignable}. \\\\ +\item Абсолютно аналогично строится тройка метафункций +\texttt{\textbf{is\_destructible, is\_trivially\_destructible, is\_nothrow\_destructible}} +и пара \texttt{\textbf{is\_swapable, is\_nothrow\_swapable}}. +\end{itemize} +\subsection{Взаимоотношения типов} +\begin{itemize} + \item \textbf{\texttt{is\_same}} - проверяет, что типы T и U совпадают. + \item \textbf{\texttt{is\_base\_of}} - проверяет, что Derived наследуется от Base или они совпадают. + \item \textbf{\texttt{is\_convertible}} - проверяет корректность следующего определения функции: + \\\\ \texttt{To test() \{ return std::declval(); \}}\\\\ + что обеспечивает возможность неявного конвертирования From в To. +\end{itemize} +\subsection{Трансформирование типов} +Также в \textbf{type\_traits} определены средства трансформации типов, имеющие интуитивно понятные названия: +\begin{itemize} +\item \texttt{\textbf{remove\_cv}} +\item \texttt{\textbf{remove\_const}} +\item \texttt{\textbf{remove\_volatile}} +\item \texttt{\textbf{add\_cv}} +\item \texttt{\textbf{add\_const}} +\item \texttt{\textbf{add\_volatile}} +\item \texttt{\textbf{remove\_reference}} +\item \texttt{\textbf{add\_lvalue\_reference}} +\item \texttt{\textbf{add\_rvalue\_reference}} +\item \texttt{\textbf{remove\_pointer}} +\item \texttt{\textbf{add\_pointer}} +\end{itemize} \ No newline at end of file From f61b66af68ff139c5319266031f9f4b55494eedb Mon Sep 17 00:00:00 2001 From: DrDet Date: Sat, 30 Dec 2017 13:24:53 +0300 Subject: [PATCH 2/9] fixed --- metaprogramming.tex | 287 ++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 173 deletions(-) diff --git a/metaprogramming.tex b/metaprogramming.tex index d9d33e6..0323a6e 100644 --- a/metaprogramming.tex +++ b/metaprogramming.tex @@ -1,18 +1,10 @@ \section{Метопрограммирование в C++11 и выше} - \subsection{Expression SFINAE} - \subsubsection{Основная суть} -Одним из самых значительных улучшений SFINAE в C++11 стало появление так называемого Expression SFINAE. \\\\ -Его суть крайне проста. -Теперь в параметрах шаблона и в сигнатуре функции разрешено использовать любые выражения. -Невозможность вычисления трактуется как SFINAE-failure. \\ -До C++03 были разрешены только constant expressions. -В случае использования других выражений всегда возникал hard-error. \\\\ -Expression SFINAE дает нам огромный потенциал, позволяя практически полностью перейти от шаблонных метафункций к привычным выражениям. \\\\ -Весьма распространенным примером использования Expression SFINAE является использование его с инфиксным оператором запятая. -Он вычисляет свои аргументы и возвращает правый.\\\\ -Используя эту особенность, мы можем перечислить все выражения, которые должны быть вычислены для SFINAE проверок, например в возвращаемом типе, и последним указать то, которое используется по факту. -Благодаря правой ассоциативности этого оператора, все перечисленные проверки будут проведены, и результатом нашего перечисления будет являться фактический возвращаемый тип.\\\\ -Посмотрим, насколько проще становится код с использованием оператора запятая и Expression SFINAE на примере функции \texttt{void print(T)}: +\subsection{SFINAE для выражений} +Одним из самых значительных улучшений SFINAE в C++11 стало появление SFINAE для выражений (Expression SFINAE). \\\\ +Его суть крайне проста. В C++11 ошибки подстановки выражений в сигнатуре функции трактуется как ошибки SFINAE, а не hard-ошибки. +В C++03 гарантировалось лишь то, что ошибки подстановки только в \textit{constant expression'ы} трактуются как ошибки SFINAE. Ошибки подстановки в не-constant expression'ы могли в зависимости от реализации трактоваться и как SFINAE-ошибки, и как hard-ошибки. \\\\ +Часто SFINAE для выражений используется вместе с оператором запятая. В невычисляемом левом аргументе оператора запятая можно перечислить выражения, которые должны участвовать в SFINAE проверках. \\\\ +Ниже приведен пример функции \texttt{void print(T)}, первая перегрузка которой умеет выводить все типы, имеющие функции cbegin() и cend(), вторая --- любые типы, умеющие выводиться в std::cout. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template decltype(std::declval().cbegin(), std::declval().cend(), void()) @@ -31,56 +23,12 @@ \section{Метопрограммирование в C++11 и выше} std::cout << "Value: " << value << "\n"; } \end{minted} -Эта функция печатает объект T или как контейнер, или как примитвный тип.\\\\ -Раньше для проверки, что T является контейнером, мы проверяли наличие у него типа \texttt{const\_iterator} с помощью метафункции \texttt{is\_const\_iterator}. -Для проверки, что T - примитвный тип использовалась метафункция \texttt{is\_integral} из \texttt{std::type\_traits}.\\\\ -Сейчас же мы можем попытаться \textquotedblright вызвать\textquotedblright\ функции cbegin и cend, возвращающие \texttt{const\_iterator}, прямо в возвращаемом типе функции. -Для проверки, что T может быть напечатан, как примитивный тип, мы можем напрямую написать выражение, передающее его в поток вывода. -Так мы достигаем абсолютной гарантии того, что он может быть напечатан. -\subsubsection{Предпосылки к нововведению} -В C++03 следующий код не компилировался, хотя стандарт того времени не запрещал подобное поведение: -\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template struct A {}; - -char xxx(int); -char xxx(float); - -template A f(T){} - -int main() -{ - f(1); -} -\end{minted} -Рассмотрим его подробнее: -\begin{enumerate} - \item Из контекста вызова f(1) успешно осуществляется вывод типа: \\ - T $\rightarrow$ int - \item Выведенный тип подставляется в нужные места: \\ - \texttt{A f(int)} - \item Компилятор анализирует эту строчку, и находит выражение, которое не является constant expression, компиляция прерывается: \\ - \texttt{xxx((int)0)} - не constant expression -\end{enumerate} - -Такое поведение было связано с технической сложностью реализации подстановки template аргументов в общем случае. -Она осуществлялась с помощью упрощенного метода семантической проверки. -Были специальные SFINAE правила, которые сильно отличались от обычной проверки выражений, вместо полной проверки семантики. Они были больше применимы именно для подстановки типов.\\\\ -В C++11 для решения этой проблемы было приянто решение разрешить вычисление обычных выражений при подстановке и большинство ошибок трактовать, как SFINAE-errors. \\ - -Был составлен список ошибок, которые не будут трактоваться как SFINAE-errors: -\begin{enumerate} -\item Ошибки, вызванные при исполнении чего-то внешнего для выражения, т.е. вычислении выражений в так называемом non-immediate context (инстанцирование шаблона, генерация определения неявно-объявленного конструктора копирования). -\item Ошибки, вызванные из-за несоблюдения - \href{https://gcc.gnu.org/onlinedocs/cpp/Implementation-limits.html}{\texttt{implementation\_limits}}. - \item Ошибки нарушения прав доступа. -\end{enumerate} -Все остальные ошибки было решено трактовать, как SFINAE-failures. \subsection{Default template arguments in function templates \& SFINAE} В С++11 появилась возможность устанавливать значения параметров шаблона функций по умолчанию. \\\\ Вывод типов в шаблонах функций с аргументами по умолчанию производится следующим образом: \begin{enumerate} \item Компилятор пытается вывести тип всех параметров шаблона. - \item Если какой-то параметр вывести не удалось, то используется его значение по умолчанию, если таковое имеется. В противном случае тип параметра не может быть выведен - deduction failure. + \item Если какой-то параметр вывести не удалось, то используется его значение по умолчанию. Если значение по умолчанию отсутствует --- происходит ошибка вывода. \end{enumerate} Небольшой пример для лучшего понимания: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -98,40 +46,42 @@ \subsection{Default template arguments in function templates \& SFINAE} f(); // f(0,0) } \end{minted} -Это нововведение можно использовать в SFINAE. Теперь мы можем писать SFINAE проверки прямо в аргументах шаблона функции по умолчанию, нисколько не меняя при этом сигнатуру функции (Шаблонная шапка не входит в сигнатуру). \\\\ +Поскольку значение по умолчанию для параметра шаблона может зависеть от других параметров шаблона, для его вычисления делается подстановка, которая может приводить к ошибкам подстановки. В C++11 требуется, чтобы ошибки подстановки в значение по умолчанию параметра шаблона трактовались как ошибки SFINAE. Это нововведение можно использовать для наложения ограничений на типы. \\\\ Рассмотрим в качестве примера реализацию функции модуля для знаковых чисел: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template ::value>>::type * = nullptr> + typename = enable_if::value>>::type> T abs(T val); \end{minted} -Мы создаем безымянный шаблонный параметр, чтобы его невозможно было вывести из контекста вызова, и присваиваем ему значение по умолчанию, являющееся SFINAE проверкой. \\\\ -Достоиннством этого метода определенно является то, что SFINAE проверка осуществляется на более ранней стадии - при \textit{выводе} типа соответствующего параметра. +Второй шаблонный параметр функции abs невозможно вывести из контекста вызова. Поэтому при вызове будет использоваться его значение по умолчанию, содержащее SFINAE-проверку. \\\\ +Достоинством этого метода является то, что SFINAE проверка осуществляется на более ранней стадии компиляции --- при \textit{выводе} типов шаблонных параметров. Это эффективно в смысле временных затрат на компиляцию.\\\\ -Но у этого метода есть недостаток. Так как шаблонная шапка не входит в сигнатуру функции, мы не можем аналогично написать версию функции для другого случая. -В нашем примере мы не можем добавить еще одну перегрузку для модуля от беззнаковых чисел: +Но у этого метода есть недостаток. Нельзя перегружать функции, отличающиеся лишь SFINAE-проверками, так как значения по умолчанию не входят в сигнатуру функции. +При попытке так сделать произойдет ошибка компиляции, поскольку две функции имеют одиннаковые сигнатуры (redefinition): \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template ::value>>::type * = nullptr> +template ::value>::type> T abs(T val) { - return -val; + return -val; } -template ::value>>::type * = nullptr> +template ::value>::type> T abs(T val) { - return val; + return val; } \end{minted} -Данный код некорректен, так как функции имеют абсолютно одинаковую сигнатуру - нарушение one definition rule. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{text} +error: redefinition of "template T abs(T)" + T abs(T val) + ^~~ +\end{minted} + \subsection{constexpr} -Еще одним весьма полезным нововведением, делающим метапрогарммирование проще, является спецификатор \textbf{constexpr}. Он способствует еще большему перенесению вычислений некоторых выражений в compile-time. \\\\ -Для начала попробуем понять, что такое constant expression. \\ -\textbf{\textit{Constant expressions}} - это такие выражения, -которые могут быть использованы для указания размера статического массива или шаблонных аргументов, которые не являются типом. -Их значение известно на момент компиляции.\\ +\subsubsection{Constant expressions} +\textbf{\textit{Constant expressions}} --- это такие выражения, значения которых должны быть известны на этапе компиляции. Например, требуется, чтобы размер статического массива и значение non-type шаблонного аргумента были constant expression'ами.\\ Пример: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int n = 1; @@ -141,93 +91,86 @@ \subsection{constexpr} std::array a2; // OK: cn is a constant expression int arr[cn]; // OK: cn is a constant expression \end{minted} -Сonstexpr гарантирует возможность использования переменных или функций в местах, где требуется \textit{constant expression}.\\\\ -Рассмотрим отдельно значение constexpr для переменных и для функций. -\subsubsection{Constexpr переменные} -Переменные, объявленные как \textbf{constexpr}, должны удовлетворять следующим требованиям: -\begin{enumerate} -\item Должны быть проинициализирована сразу при объявлении. -\item Выражение в инициализации должно быть \textit{constant expression}. -\end{enumerate} -\textbf{Constexpr} переменная по смыслу включает себя значение \textbf{const} переменной, но не наоборот. -Вообще говоря, не все \textbf{const} переменные являются \textit{constant expressions}.\\\\ -Отличие \textbf{constexpr} переменных от \textbf{const} в том, что \textbf{const} гарантирует лишь то, что значение переменной не будет изменено. Оно может быть неизвестно на момент компиляции.\\\\ -Пример: -\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -int f(int x) -{ - if (x > 0) - return 10; - else - return 5; -} -int four() +Переменные, помеченные как \textbf{const}, не обязательно являются constant expression'ами.\\ Например: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int compute_value() { - return 4; + int result; + std::cin >> result; + return result; } -const int m = four(); -int a[m]; // ошибка компиляции: m - не constant expression +int const value = compute_value(); +int arr[value]; // error: value is not a constant expression -int main() -{ - int custom_v; - std::cin >> custom_v; - const int N = f(custom_v); - int b[N]; // ошибка компиляции: N - не constant expression - return 0; -} \end{minted} -\texttt{N} никак не может быть известно на момент компиляции, несмотря на то, что является \textbf{const}. -Более того, \texttt{m} несмотря на свою логическую константность, не является \textit{constant expression}. \\\\ -Const переменная является \textit{constant expression} только если: +В данном случае \textbf{const} означает лишь то, что переменную невозможно изменять. Конкретное значение этой переменной известно лишь после запуска программы. Const переменная является \textit{constant expression} только если: \begin{enumerate} \item Она является интегральным типом или перечислением. (см. таблицу \ref{tab:types} категорий типов) \item Выражение, которым инициализируется начальное значение - \textit{constant expression}. \end{enumerate} -Пример: + +Результат вызова функции в C++03 никогда не является constant expression'ом: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -int main() -{ - const int N = 3; - int numbers[N] = {1, 2, 3}; // N is constant expression - return 0; -} +int arr[max(3, 4)]; // error: max(3, 4) is not a constant expression \end{minted} -В данном случае N удовлетворяет тербованиям \textit{constant expression}. -\subsubsection{Constexpr функции} -Довольно полезная вещь, не имеющая аналогов до C++11. \\ -Объявление функции как \textbf{constexpr} гарантирует, что она является \textit{constant expression} и, как следствие, ее значение может быть посчитано на этапе компиляции. \\\\ -На \textbf{constexpr} функции накладываюстя довольно серьзеные требования: + +Введенное в C++11 ключевое слово \textbf{constexpr} решает две задачи: \begin{enumerate} -\item Функция должна возвращать значение, являющееся \textbf{constexpr}. -\item Аргументы функции должны быть \textbf{constexpr}. -\item Функция может содержать следующие конструкции: \texttt{typedef}, различные вариации \texttt{using}, \texttt{static\_assert}, пустое выражение(;) и, собственно, \texttt{return}. -\item Функция может содержать не более одного \texttt{return}. +\item Для переменных --- позволяет указать, что переменная является \textit{constant expression}. +\item Для функций --- позволяет указать, что результат вызова функции является \textit{constant expression}. \end{enumerate} -И дополнительно для \textbf{constexpr} функций-членов: + +\subsubsection{Constexpr переменные} +Переменные, объявленные как \textbf{constexpr}, должны удовлетворять следующим требованиям: \begin{enumerate} -\item Метод не может быть виртуальным. -\item Метод может быть объявлен с \texttt{=default} или c \texttt{=delete}. -\item \textbf{constexpr} метод является \textbf{const}, по умолчанию. +\item Должны быть проинициализированы сразу при объявлении. +\item Выражение в инициализации должно быть \textit{constant expression}. +\end{enumerate} +Использование \textbf{Constexpr} переменной является constant expression'ом. \textbf{Constexpr} переменные неявно являются \textbf{const} переменными. + +% TODO: вопросы: как constexpr переменные участвуют в линковке? что произойдет если взять их адрес? +% TODO: если адрес брать можно, то значит они работают как inline переменные? +% TODO: можно ли им написать дефинишн? constexpr int a = 5; constexpr int mytype::a; + +\subsubsection{Constexpr функции} +%TODO: уточнить формулировку +Объявление функции как \textbf{constexpr} означает, что если все её аргмуенты являются \textit{constant expression}'ами, то возвращаемое значение может быть посчитано на этапе компиляции. Если значение хотя бы одного аргумента, переданного функции, не известно на момент компиляции, то возвращаемое значение функции вычисляется в run-time, ошибки компиляции не происходит.\\\\ +%TODO: рассмотреть ограничения с++14 +В C++14 \textbf{constexpr} функции могут содержать в теле следующее: +\begin{enumerate} + \item Любые выражения, кроме \texttt{static} или \texttt{thread\_local} переменных. + \item Конструкции if и switch. + \item Любой цикл (for для диапазонов). + \item Выражения, изменяющие значения объектов, если время жизни этих объектов началось в \textbf{constexpr} функции. \end{enumerate} Возможна рекурсия. \\\\ -То есть, основная суть \textbf{constexpr} функции должна содержаться в единственном return statement. -Из-за таких ограничений в \textbf{constexpr} функциях повсеместно используют тернарный оператор, -что делает стиль написания похожим на написание программ на функциональном языке программирования. \\\\ -Constexpr функции могут быть использованы для метапрограммирования в дополнение к привычным шаблонам. Пример: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template -constexpr bool isBaseOf() +constexpr int sum(int n) { + int sum = 0; + for (int i = 1; i <= n; ++i) + { + if (i % 2) + sum += i; + } + return sum; +} + +int main() { - return std::is_base_of::value; + int a[sum(10)]; // OK: 10 is a constant expression, so sum(10) - constant expression + int x; + std::cin >> x; + int y = sum(x); + int b[sum(x)]; // compilation error: x is not a constant expression, so sum(x) is not a constant expression + return 0; } \end{minted} -Теперь, чтобы проверить является ли один класс базой для другого, мы можем просто вызвать функцию: \\ -\texttt{isBaseOf()} \\ -Это значительно улучшает код, делая его гораздо проще для понимания и читабельнее. +В строчке 13 в \textbf{constexpr} функцию \texttt{sum} передается \textit{constant expression}, поэтому она считается на этапе компиляции. \\\\ +В строчке 16 --- передается не \textit{constant expression}. Ошибки компиляции не происходит, несмотря на то, что \texttt{sum} помечена как \textbf{constexpr}. Функция просто считается в run-time. \\\\ +Строчка 17 подтверждает, что \texttt{sum(x)} не является \textbf{constant expression}. \subsection{if constexpr} В C++17 появилась конструкция \textbf{if constexpr}. \\\\ Позволяет, используя прямой синтаксис, создавать compile-time условие: @@ -238,8 +181,9 @@ \subsection{if constexpr} } else /../ \end{minted} condition должно являться \textit{constant expression} типа bool. \\\\ -В отличии от обычного if statement-a не треует корректности(well-formed) выражений в false ветке. \\\\ -Рассмотрим как пример функцию модуля знаковых/беззнаковых чисел с использование if constexpr: +% TODO: насколько корректным должен быть код в false ветке? +В отличии от обычного if-statement'a, \textbf{if constexpr} не требует корректности выражений зависимых от шаблонного параметра в false-ветке. \\\\ +Рассмотрим как пример функцию модуля знаковых/беззнаковых чисел с использованием \textbf{if constexpr}: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template T abs(T value) { @@ -250,12 +194,14 @@ \subsection{if constexpr} } } \end{minted} -Как мы можем видеть, код по синтаксису не отличается от run-time версии с обычным условием. Никаких двух специализаций шаблона функции. \\\\ -Казалось бы, такое мощное средство позволяет нам полностью избавиться от необходимости -использования косвенных SFINAE проверок, использующих шаблоны. Но в действительности, это не так. +% TODO: почему компилятор не может соптимизировать обычный if? +Приведенный код остается корректным, даже если T не имеет унарного оператора минус. \\\\ +Попытка сделать это с обычным if-statement'ом, приведет к ошибке компиляции. \\\\ Обычный if-statement требует корректности всех выражений, даже если они встречаются в недостижимом коде. Поэтому в привиденном примере требуется использовать \textbf{if constexpr}, несмотря на то, что condition является \textit{constant expression'ом} и некорректный код недостижим.\\\\ +Казалось бы, такое мощное средство позволяет полностью избавиться от необходимости +использования косвенных SFINAE проверок, использующих шаблоны, и перейти к более удобному синтаксису. Но в действительности, это не так. If constexpr не может в полной мере заменить SFINAE. \\\\ -Представим, что мы хотим сделать compile-time условие, в зависимости от которого перегрузки от некоторых типов, назовем их \textquotedblright плохими\textquotedblright, не будут генерироваться в коде вовсе. -С помощью SFINAE мы с легкостью можем сделать это так: +Пусть требуется сделать compile-time условие, в зависимости от которого перегрузки от некоторых типов, назовем их \textquotedblright плохими\textquotedblright, не будут генерироваться в коде вовсе. +С помощью SFINAE можно сделать это так: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template enable_if::value>::type foo(T) { @@ -263,8 +209,8 @@ \subsection{if constexpr} } \end{minted} Вызов \texttt{foo(Goo\_Type)} будет совершен успешно, вызов \texttt{foo(Bad\_Type)} приводит к ошибке компиляции. -Все работает так, как мы и хотели. \\\\ -Теперь попробуем переписать с использованием \textbf{if constexpr} вместо SFINAE: +Все работает так, как и требовалось. \\\\ +Рассмотрим версию с использованием \textbf{if constexpr} вместо SFINAE: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template void foo(T x) @@ -276,39 +222,32 @@ \subsection{if constexpr} // cant write something to discard call from bad_type } \end{minted} -Заметим, что во втором случае вызов \texttt{foo(Bad\_Type)} - будет корректным и не приведет к ошибке компиляции. \\ +Во втором случае вызов \texttt{foo(Bad\_Type)} - будет корректным, не приведет к ошибке компиляции. \\ Дело в том, что в версии с \textbf{if constexpr} поверка осуществляется на более поздней стадии - на стадии, когда мы уже внутри тела функции, когда код уже сгенерирован. -Мы не имеем средств, чтобы \textquotedblright подавить\textquotedblright\ вызов, будучи внутри функции. +Язык не содержит средств, чтобы \textquotedblright подавить\textquotedblright\ вызов, изнутри функции. \subsection{Static assertions} Предоставляет возможность писать compile-time asserts. \\\\ Синтаксис: \\ \texttt{static\_assert(condition, message)} \\\\ где \texttt{condition} - это \textit{constant expression} типа bool, \texttt{message} - строковый литерал, сообщение выдаваемое при срабатывании ассерта. \\\\ В случае condition $=$ false, происходит ошибка компиляции, печатается сообщение message, иначе ничего не происходит. \\\\ -Можно рассматривать \textbf{static assertions} как альтернативу SFINAE, аналогично \textbf{if constexpr}, рассмотренному ранее. \\ -Модернизируем предыдущий пример, используя static assertion: +% TODO: пример не корректный +% TODO: разобраться, почему static_assert(false) не работает и попытаться это описать +Применяется для compile-time проверки свойств, зависящих от параметра шаблона: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template -void foo(T x) -{ - if constexpr(is_good::value) { - /*...body...*/ - } else { - static_assert(false, "No function foo() for Bad Types") - } +template +Integral foo(Integral x, Integral y) { + static_assert(std::is_integral::value, "foo() parameter must be an integral type."); } \end{minted} -Теперь при вызове \texttt{foo(Bad\_Type)} действительно имеем ошибку компиляции, но это, опять же, не то, что нужно. \\ -Перегрузка от плохого типа на самом деле есть, но при входе в нее происходит ошибка компиляции. Это не то же самое, что перегрузки нет. \\ -К примеру, SFINAE проверка на наличие перегрузки от плохого типа в нашем случае будет возвращать \texttt{true}, что противоречит тому, что мы хотели. \subsection{Template variables} В С++14 появилась возможность создавать шаблонные переменные. \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template constexpr T pi = T(3.14159265); -float x = pi; -float y = pi; +constexpr float x = pi; +constexpr float y = pi; \end{minted} Шаблонные переменные обязаны быть \textbf{constexpr}. Допускаются специализациии. \\\\ Наиболее часто применяются для упрощения метафункций из \texttt{std::type\_traits}. \\ @@ -360,10 +299,12 @@ \section{std::type\_traits} \texttt{std::type\_traits} - это библиотека, которая содержит множество полезных метафункции для работы с классами. В этой части будет проведен её обзор. \\\\ \subsection{Вспомогательные типы} \begin{itemize} - \item \texttt{\textbf{integral\_constant} \\\\ - Создает константу времени компиляции типа T со значением v. - \item \texttt{\textbf{true\_type}} = integral\_constant - \item \texttt{\textbf{false\_type}} = integral\_constant + \item \texttt{\textbf{integral\_constant} \\ Константа времени компиляции типа T со значением v. \\ + % TODO: очень важно, что is_same (integral_constant) содержит value. + Содержит поле: \\ +\texttt{\textbf{static constexpr T value = v;}} + \item \texttt{\textbf{true\_type}} определен как integral\_constant + \item \texttt{\textbf{false\_type}} определен как integral\_constant \end{itemize} Довольно удобно наследоваться от последних двух при написании своих метафункций: \begin{minted} [frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -511,4 +452,4 @@ \subsection{Трансформирование типов} \item \texttt{\textbf{add\_rvalue\_reference}} \item \texttt{\textbf{remove\_pointer}} \item \texttt{\textbf{add\_pointer}} -\end{itemize} \ No newline at end of file +\end{itemize} From 84855074c5fe1eff326e84fb3607a9efaaf818ba Mon Sep 17 00:00:00 2001 From: DrDet Date: Sat, 30 Dec 2017 13:42:07 +0300 Subject: [PATCH 3/9] fixed --- metaprogramming.tex | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/metaprogramming.tex b/metaprogramming.tex index 0323a6e..942f827 100644 --- a/metaprogramming.tex +++ b/metaprogramming.tex @@ -121,7 +121,10 @@ \subsubsection{Constant expressions} \item Для переменных --- позволяет указать, что переменная является \textit{constant expression}. \item Для функций --- позволяет указать, что результат вызова функции является \textit{constant expression}. \end{enumerate} - +% TODO: вопросы: как constexpr переменные участвуют в линковке? что произойдет если взять их адрес? +% TODO: если адрес брать можно, то значит они работают как inline переменные? +% TODO: можно ли им написать дефинишн? constexpr int a = 5; constexpr int mytype::a; +По стандарту \textbf{constexpr} переменные и \textbf{constexpr} функции неявно являются \texttt{inline}. \subsubsection{Constexpr переменные} Переменные, объявленные как \textbf{constexpr}, должны удовлетворять следующим требованиям: \begin{enumerate} @@ -129,11 +132,6 @@ \subsubsection{Constexpr переменные} \item Выражение в инициализации должно быть \textit{constant expression}. \end{enumerate} Использование \textbf{Constexpr} переменной является constant expression'ом. \textbf{Constexpr} переменные неявно являются \textbf{const} переменными. - -% TODO: вопросы: как constexpr переменные участвуют в линковке? что произойдет если взять их адрес? -% TODO: если адрес брать можно, то значит они работают как inline переменные? -% TODO: можно ли им написать дефинишн? constexpr int a = 5; constexpr int mytype::a; - \subsubsection{Constexpr функции} %TODO: уточнить формулировку Объявление функции как \textbf{constexpr} означает, что если все её аргмуенты являются \textit{constant expression}'ами, то возвращаемое значение может быть посчитано на этапе компиляции. Если значение хотя бы одного аргумента, переданного функции, не известно на момент компиляции, то возвращаемое значение функции вычисляется в run-time, ошибки компиляции не происходит.\\\\ From 5c8b18712677946a1b0f83399351a7d69c9eec93 Mon Sep 17 00:00:00 2001 From: DrDet Date: Sat, 30 Dec 2017 14:02:31 +0300 Subject: [PATCH 4/9] fixed --- metaprogramming.tex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/metaprogramming.tex b/metaprogramming.tex index 942f827..509e194 100644 --- a/metaprogramming.tex +++ b/metaprogramming.tex @@ -110,7 +110,6 @@ \subsubsection{Constant expressions} \item Она является интегральным типом или перечислением. (см. таблицу \ref{tab:types} категорий типов) \item Выражение, которым инициализируется начальное значение - \textit{constant expression}. \end{enumerate} - Результат вызова функции в C++03 никогда не является constant expression'ом: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int arr[max(3, 4)]; // error: max(3, 4) is not a constant expression @@ -124,14 +123,14 @@ \subsubsection{Constant expressions} % TODO: вопросы: как constexpr переменные участвуют в линковке? что произойдет если взять их адрес? % TODO: если адрес брать можно, то значит они работают как inline переменные? % TODO: можно ли им написать дефинишн? constexpr int a = 5; constexpr int mytype::a; -По стандарту \textbf{constexpr} переменные и \textbf{constexpr} функции неявно являются \texttt{inline}. +По стандарту \textbf{constexpr} переменные и \textbf{constexpr} функции неявно являются \texttt{inline}. Не могут иметь внешнюю линковку. \subsubsection{Constexpr переменные} Переменные, объявленные как \textbf{constexpr}, должны удовлетворять следующим требованиям: \begin{enumerate} \item Должны быть проинициализированы сразу при объявлении. \item Выражение в инициализации должно быть \textit{constant expression}. \end{enumerate} -Использование \textbf{Constexpr} переменной является constant expression'ом. \textbf{Constexpr} переменные неявно являются \textbf{const} переменными. +Использование \textbf{constexpr} переменной является \textit{constant expression}'ом. \textbf{Constexpr} переменные неявно являются \textbf{const} переменными. \subsubsection{Constexpr функции} %TODO: уточнить формулировку Объявление функции как \textbf{constexpr} означает, что если все её аргмуенты являются \textit{constant expression}'ами, то возвращаемое значение может быть посчитано на этапе компиляции. Если значение хотя бы одного аргумента, переданного функции, не известно на момент компиляции, то возвращаемое значение функции вычисляется в run-time, ошибки компиляции не происходит.\\\\ From 29ad71c66b377726e7fb31002aac6eef536dac6e Mon Sep 17 00:00:00 2001 From: DrDet Date: Sat, 30 Dec 2017 14:37:16 +0300 Subject: [PATCH 5/9] fixed --- metaprogramming.tex | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/metaprogramming.tex b/metaprogramming.tex index 509e194..1d01faa 100644 --- a/metaprogramming.tex +++ b/metaprogramming.tex @@ -120,17 +120,17 @@ \subsubsection{Constant expressions} \item Для переменных --- позволяет указать, что переменная является \textit{constant expression}. \item Для функций --- позволяет указать, что результат вызова функции является \textit{constant expression}. \end{enumerate} -% TODO: вопросы: как constexpr переменные участвуют в линковке? что произойдет если взять их адрес? -% TODO: если адрес брать можно, то значит они работают как inline переменные? -% TODO: можно ли им написать дефинишн? constexpr int a = 5; constexpr int mytype::a; -По стандарту \textbf{constexpr} переменные и \textbf{constexpr} функции неявно являются \texttt{inline}. Не могут иметь внешнюю линковку. \subsubsection{Constexpr переменные} Переменные, объявленные как \textbf{constexpr}, должны удовлетворять следующим требованиям: \begin{enumerate} \item Должны быть проинициализированы сразу при объявлении. \item Выражение в инициализации должно быть \textit{constant expression}. \end{enumerate} -Использование \textbf{constexpr} переменной является \textit{constant expression}'ом. \textbf{Constexpr} переменные неявно являются \textbf{const} переменными. +Использование \textbf{constexpr} переменной является \textit{constant expression}'ом. \textbf{Constexpr} переменные неявно являются \textbf{const} переменными. \\\\ +% TODO: вопросы: как constexpr переменные участвуют в линковке? что произойдет если взять их адрес? +% TODO: если адрес брать можно, то значит они работают как inline переменные? +% TODO: можно ли им написать дефинишн? constexpr int a = 5; constexpr int mytype::a; +Не участвуют в линковке, так как нельзя разделить объявление и определение \textbf{constexpr} переменной. Подставляются во все места использования подобно \textbf{\texttt{inline}}. \subsubsection{Constexpr функции} %TODO: уточнить формулировку Объявление функции как \textbf{constexpr} означает, что если все её аргмуенты являются \textit{constant expression}'ами, то возвращаемое значение может быть посчитано на этапе компиляции. Если значение хотя бы одного аргумента, переданного функции, не известно на момент компиляции, то возвращаемое значение функции вычисляется в run-time, ошибки компиляции не происходит.\\\\ @@ -143,6 +143,7 @@ \subsubsection{Constexpr функции} \item Выражения, изменяющие значения объектов, если время жизни этих объектов началось в \textbf{constexpr} функции. \end{enumerate} Возможна рекурсия. \\\\ +По стандарту \textbf{constexpr} функции неявно являются \texttt{inline}. Не могут иметь внешнюю линковку. \\\\ Пример: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} constexpr int sum(int n) { @@ -220,7 +221,7 @@ \subsection{if constexpr} } \end{minted} Во втором случае вызов \texttt{foo(Bad\_Type)} - будет корректным, не приведет к ошибке компиляции. \\ -Дело в том, что в версии с \textbf{if constexpr} поверка осуществляется на более поздней стадии - на стадии, когда мы уже внутри тела функции, когда код уже сгенерирован. +Дело в том, что в версии с \textbf{if constexpr} поверка осуществляется на более поздней стадии - на стадии, уже внутри тела функции, когда код уже сгенерирован. Язык не содержит средств, чтобы \textquotedblright подавить\textquotedblright\ вызов, изнутри функции. \subsection{Static assertions} Предоставляет возможность писать compile-time asserts. \\\\ From 83d495a2da62aee2bc683be3d7aed6c9a4c5459a Mon Sep 17 00:00:00 2001 From: DrDet Date: Wed, 3 Jan 2018 23:46:39 +0300 Subject: [PATCH 6/9] final version --- metaprogramming.tex | 187 ++++++++++++++++++++++---------------------- 1 file changed, 93 insertions(+), 94 deletions(-) diff --git a/metaprogramming.tex b/metaprogramming.tex index 1d01faa..8d3cd2d 100644 --- a/metaprogramming.tex +++ b/metaprogramming.tex @@ -1,7 +1,7 @@ -\section{Метопрограммирование в C++11 и выше} +\section{Метапрограммирование в C++11 и выше} \subsection{SFINAE для выражений} Одним из самых значительных улучшений SFINAE в C++11 стало появление SFINAE для выражений (Expression SFINAE). \\\\ -Его суть крайне проста. В C++11 ошибки подстановки выражений в сигнатуре функции трактуется как ошибки SFINAE, а не hard-ошибки. +Его суть заключается в том, что ошибки подстановки выражений в сигнатуре функции трактуется как ошибки SFINAE, а не как hard-ошибки. В C++03 гарантировалось лишь то, что ошибки подстановки только в \textit{constant expression'ы} трактуются как ошибки SFINAE. Ошибки подстановки в не-constant expression'ы могли в зависимости от реализации трактоваться и как SFINAE-ошибки, и как hard-ошибки. \\\\ Часто SFINAE для выражений используется вместе с оператором запятая. В невычисляемом левом аргументе оператора запятая можно перечислить выражения, которые должны участвовать в SFINAE проверках. \\\\ Ниже приведен пример функции \texttt{void print(T)}, первая перегрузка которой умеет выводить все типы, имеющие функции cbegin() и cend(), вторая --- любые типы, умеющие выводиться в std::cout. @@ -35,7 +35,7 @@ \subsection{Default template arguments in function templates \& SFINAE} template void f(T t = 0, U u = 0); -char c; //any symbol +char c; //any symbol void g() { @@ -53,11 +53,12 @@ \subsection{Default template arguments in function templates \& SFINAE} typename = enable_if::value>>::type> T abs(T val); \end{minted} -Второй шаблонный параметр функции abs невозможно вывести из контекста вызова. Поэтому при вызове будет использоваться его значение по умолчанию, содержащее SFINAE-проверку. \\\\ +Второй шаблонный параметр функции \texttt{\textbf{abs}} невозможно вывести из контекста вызова. Поэтому при вызове будет использоваться его значение по умолчанию, содержащее SFINAE-проверку. \\\\ Достоинством этого метода является то, что SFINAE проверка осуществляется на более ранней стадии компиляции --- при \textit{выводе} типов шаблонных параметров. -Это эффективно в смысле временных затрат на компиляцию.\\\\ +Это эффективно в смысле временных затрат на компиляцию, по сравнению с использованием \textbf{\texttt{enable\_if}} в сигнатуре +функции.\\\\ Но у этого метода есть недостаток. Нельзя перегружать функции, отличающиеся лишь SFINAE-проверками, так как значения по умолчанию не входят в сигнатуру функции. -При попытке так сделать произойдет ошибка компиляции, поскольку две функции имеют одиннаковые сигнатуры (redefinition): +При попытке так сделать произойдет ошибка компиляции, поскольку две функции имеют одинаковые сигнатуры (redefinition): \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template ::value>::type> @@ -79,17 +80,17 @@ \subsection{Default template arguments in function templates \& SFINAE} ^~~ \end{minted} -\subsection{constexpr} +\subsection{Constexpr} \subsubsection{Constant expressions} -\textbf{\textit{Constant expressions}} --- это такие выражения, значения которых должны быть известны на этапе компиляции. Например, требуется, чтобы размер статического массива и значение non-type шаблонного аргумента были constant expression'ами.\\ +\textbf{\textit{Constant expressions}} --- это такие выражения, значения которых должны быть известны на этапе компиляции. Например, требуется, чтобы размер статического массива и значение non-type шаблонного аргумента были \textit{constant expression}'ами.\\ Пример: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int n = 1; -std::array a1; // error: n is not a constant expression -int arr[n]; // error: n is not a constant expression +std::array a1; // error: n is not a constant expression +int arr[n]; // error: n is not a constant expression const int cn = 2; -std::array a2; // OK: cn is a constant expression -int arr[cn]; // OK: cn is a constant expression +std::array a2; // OK: cn is a constant expression +int arr[cn]; // OK: cn is a constant expression \end{minted} Переменные, помеченные как \textbf{const}, не обязательно являются constant expression'ами.\\ Например: @@ -105,10 +106,10 @@ \subsubsection{Constant expressions} int arr[value]; // error: value is not a constant expression \end{minted} -В данном случае \textbf{const} означает лишь то, что переменную невозможно изменять. Конкретное значение этой переменной известно лишь после запуска программы. Const переменная является \textit{constant expression} только если: +В данном случае \textbf{const} означает лишь то, что переменную невозможно изменять. Конкретное значение этой переменной известно лишь после запуска программы. \textbf{Const} переменная является \textit{constant expression} только если: \begin{enumerate} \item Она является интегральным типом или перечислением. (см. таблицу \ref{tab:types} категорий типов) - \item Выражение, которым инициализируется начальное значение - \textit{constant expression}. + \item Выражение, которым инициализируется начальное значение --- \textit{constant expression}. \end{enumerate} Результат вызова функции в C++03 никогда не является constant expression'ом: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -127,23 +128,30 @@ \subsubsection{Constexpr переменные} \item Выражение в инициализации должно быть \textit{constant expression}. \end{enumerate} Использование \textbf{constexpr} переменной является \textit{constant expression}'ом. \textbf{Constexpr} переменные неявно являются \textbf{const} переменными. \\\\ -% TODO: вопросы: как constexpr переменные участвуют в линковке? что произойдет если взять их адрес? -% TODO: если адрес брать можно, то значит они работают как inline переменные? -% TODO: можно ли им написать дефинишн? constexpr int a = 5; constexpr int mytype::a; -Не участвуют в линковке, так как нельзя разделить объявление и определение \textbf{constexpr} переменной. Подставляются во все места использования подобно \textbf{\texttt{inline}}. +Как и \textbf{const} переменные, по умолчанию имеют внутреннюю линковку, в каждой единице трансляции имеют собственный адрес. \\\\ +В C++17 появился способ устанавливать внешнюю линковку для \textbf{constexpr} переменных: +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +//a.cpp +struct X { + static constexpr int x = 42; //declaration +} +\end{minted} +\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +//b.cpp +constexpr int X::x; //definition +\end{minted} +Такой код корректен, так как в С++17 все \texttt{static} переменные, являющиеся member'ами какого-то класса, неявно являются inline. \subsubsection{Constexpr функции} -%TODO: уточнить формулировку -Объявление функции как \textbf{constexpr} означает, что если все её аргмуенты являются \textit{constant expression}'ами, то возвращаемое значение может быть посчитано на этапе компиляции. Если значение хотя бы одного аргумента, переданного функции, не известно на момент компиляции, то возвращаемое значение функции вычисляется в run-time, ошибки компиляции не происходит.\\\\ -%TODO: рассмотреть ограничения с++14 +Объявление функции как \textbf{constexpr} означает, что если все её аргументы являются \textit{constant expression}'ами, то возвращаемое значение может быть посчитано на этапе компиляции. Если значение хотя бы одного аргумента, переданного функции, не известно на момент компиляции, то возвращаемое значение функции вычисляется в run-time, ошибки компиляции не происходит.\\\\ В C++14 \textbf{constexpr} функции могут содержать в теле следующее: \begin{enumerate} \item Любые выражения, кроме \texttt{static} или \texttt{thread\_local} переменных. \item Конструкции if и switch. - \item Любой цикл (for для диапазонов). + \item Циклы. \item Выражения, изменяющие значения объектов, если время жизни этих объектов началось в \textbf{constexpr} функции. \end{enumerate} Возможна рекурсия. \\\\ -По стандарту \textbf{constexpr} функции неявно являются \texttt{inline}. Не могут иметь внешнюю линковку. \\\\ +По стандарту \textbf{constexpr} функции неявно являются \texttt{inline}. \\\\ Пример: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} constexpr int sum(int n) { @@ -158,79 +166,70 @@ \subsubsection{Constexpr функции} int main() { - int a[sum(10)]; // OK: 10 is a constant expression, so sum(10) - constant expression + int a[sum(10)]; // OK: 10 is a constant expression, so sum(10) - constant expression int x; std::cin >> x; int y = sum(x); - int b[sum(x)]; // compilation error: x is not a constant expression, so sum(x) is not a constant expression + int b[sum(x)]; // compilation error: x is not a constant expression, so sum(x) is not a constant expression return 0; } \end{minted} -В строчке 13 в \textbf{constexpr} функцию \texttt{sum} передается \textit{constant expression}, поэтому она считается на этапе компиляции. \\\\ -В строчке 16 --- передается не \textit{constant expression}. Ошибки компиляции не происходит, несмотря на то, что \texttt{sum} помечена как \textbf{constexpr}. Функция просто считается в run-time. \\\\ -Строчка 17 подтверждает, что \texttt{sum(x)} не является \textbf{constant expression}. -\subsection{if constexpr} -В C++17 появилась конструкция \textbf{if constexpr}. \\\\ -Позволяет, используя прямой синтаксис, создавать compile-time условие: +В строке 13 в \textbf{constexpr} функцию \texttt{sum} передается \textit{constant expression}, поэтому она считается на этапе компиляции. \\\\ +В строке 16 передается не \textit{constant expression}. Функция просто считается в run-time. \\\\ +Ошибка компиляции в строке 17 подтверждает, что \texttt{sum(x)} не является \textbf{constant expression}. +\subsection{If constexpr} +Пусть требуется написать функцию модуля для всех численных типов. Возможна следующая реализация: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -if constexpr(/*condition*/) +template +T abs(T value) { - /*...*/ -} else /../ + if (is_signed::value) + return (value < 0 ? -value : value); + else + return value; +} \end{minted} -condition должно являться \textit{constant expression} типа bool. \\\\ -% TODO: насколько корректным должен быть код в false ветке? -В отличии от обычного if-statement'a, \textbf{if constexpr} не требует корректности выражений зависимых от шаблонного параметра в false-ветке. \\\\ -Рассмотрим как пример функцию модуля знаковых/беззнаковых чисел с использованием \textbf{if constexpr}: +С помощью обычного if-statement'а и метафункции \texttt{\textbf{is\_signed}} тип проверяется на принадлежность к категории знаковых. В зависимости от этой проверки возвращается либо значение переменной, либо ему противоположное. \\\\ +Условие в if-statement'е являтся \textit{constant expression}'ом, поэтому уже на этапе компиляции известно, какая ветка if-statement'а будет выполняться всегда, а какая является недостижимой. Несмотря на это, код в недостижимой ветке обычного if-statement'а должен быть корректным в любом случае. \\\\ +Это влечет проблемы, если функция \textbf{\texttt{abs}} вызывается от пользовательского числового типа, не имеющего унарного оператора минус (например unsigned\_big\_integer). Подобный вызов приведет к ошибке компиляции. \\\\ +Эту проблему решает конструкция \textbf{if constexpr}, появившаяся в C++17: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template -T abs(T value) { - if constexpr(is_signed::value) { +T abs(T value) +{ + if constexpr(is_signed::value) return (value < 0 ? -value : value); - } else { - reutrn value; - } + else + return value; } \end{minted} -% TODO: почему компилятор не может соптимизировать обычный if? -Приведенный код остается корректным, даже если T не имеет унарного оператора минус. \\\\ -Попытка сделать это с обычным if-statement'ом, приведет к ошибке компиляции. \\\\ Обычный if-statement требует корректности всех выражений, даже если они встречаются в недостижимом коде. Поэтому в привиденном примере требуется использовать \textbf{if constexpr}, несмотря на то, что condition является \textit{constant expression'ом} и некорректный код недостижим.\\\\ +В отличии от обычного if-statement'a, \textbf{if constexpr} не требует корректности выражений зависимых от шаблонного параметра в false-ветке.\\\\ +Таким образом, \textbf{if constexpr} позволяет создавать compile-time условие. Условие должно являться \textit{constant expression} типа bool.\\\\ Казалось бы, такое мощное средство позволяет полностью избавиться от необходимости -использования косвенных SFINAE проверок, использующих шаблоны, и перейти к более удобному синтаксису. Но в действительности, это не так. -If constexpr не может в полной мере заменить SFINAE. \\\\ -Пусть требуется сделать compile-time условие, в зависимости от которого перегрузки от некоторых типов, назовем их \textquotedblright плохими\textquotedblright, не будут генерироваться в коде вовсе. -С помощью SFINAE можно сделать это так: +использования косвенных SFINAE-проверок, использующих шаблоны, и перейти к более удобному синтаксису. Но, в действительности, это не так. +\textbf{If constexpr} не может в полной мере заменить SFINAE. \\\\ +При использовании \textbf{if constexpr} compile-time проверка осуществляется на более поздней стадии --- уже внутри тела функции. В случае если перегрузка от данного типа не имеет смысла и не должна быть сгенерирована вовсе, \textbf{if constexpr} все равно позволит коду функции сгенерироваться.\\\\ +В привиденном выше примере SFINAE-проверка на наличие метода \textbf{\texttt{abs}} от \textbf{\texttt{std::string}} вернет true, что противоречит желаемой действительности. \\\\ +Полностью корректная версия должна содержать SFINAE-проверку: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template -enable_if::value>::type foo(T) { - /*...body...*/ +template +decltype(enable_if::value>::type, T) abs(T value) +{ + return (value < 0 ? -value : value); } -\end{minted} -Вызов \texttt{foo(Goo\_Type)} будет совершен успешно, вызов \texttt{foo(Bad\_Type)} приводит к ошибке компиляции. -Все работает так, как и требовалось. \\\\ -Рассмотрим версию с использованием \textbf{if constexpr} вместо SFINAE: -\begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + template -void foo(T x) +decltype(enable_if::value>::type, T) abs(T value) { - if constexpr(is_good::value) - { - /*...body...*/ - } - // cant write something to discard call from bad_type + return value; } \end{minted} -Во втором случае вызов \texttt{foo(Bad\_Type)} - будет корректным, не приведет к ошибке компиляции. \\ -Дело в том, что в версии с \textbf{if constexpr} поверка осуществляется на более поздней стадии - на стадии, уже внутри тела функции, когда код уже сгенерирован. -Язык не содержит средств, чтобы \textquotedblright подавить\textquotedblright\ вызов, изнутри функции. \subsection{Static assertions} Предоставляет возможность писать compile-time asserts. \\\\ Синтаксис: \\ \texttt{static\_assert(condition, message)} \\\\ -где \texttt{condition} - это \textit{constant expression} типа bool, \texttt{message} - строковый литерал, сообщение выдаваемое при срабатывании ассерта. \\\\ +где \texttt{condition} --- это \textit{constant expression} типа bool, \texttt{message} --- строковый литерал, сообщение выдаваемое при срабатывании ассерта. \\\\ В случае condition $=$ false, происходит ошибка компиляции, печатается сообщение message, иначе ничего не происходит. \\\\ -% TODO: пример не корректный -% TODO: разобраться, почему static_assert(false) не работает и попытаться это описать Применяется для compile-time проверки свойств, зависящих от параметра шаблона: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template @@ -247,7 +246,7 @@ \subsection{Template variables} constexpr float x = pi; constexpr float y = pi; \end{minted} -Шаблонные переменные обязаны быть \textbf{constexpr}. Допускаются специализациии. \\\\ +Шаблонные переменные обязаны быть \textbf{constexpr}. Допускаются специализации. \\\\ Наиболее часто применяются для упрощения метафункций из \texttt{std::type\_traits}. \\ Рассмотрим на примере \texttt{is\_same}: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -257,20 +256,21 @@ \subsection{Template variables} Теперь вместо \texttt{is\_same::value} можем писать просто \texttt{is\_same\_v}. \\\\ В C++14 все метафункции, возвращающие значение, имеют соответствующие шаблонные переменные с суффиксом \texttt{\_v}. \subsection{Type alias \& Alias template} -\textbf{Type alias} - это псевдоним типа, объявленного ранее(подобно \texttt{typedef}). \textbf{Template alias} - псевдоним для шаблона.\\\\ +\textbf{Type alias} --- это псевдоним типа, объявленного ранее(подобно \texttt{typedef}). \textbf{Template alias} --- псевдоним для шаблона.\\\\ \textbf{Type alias} можно использовать, как альтернативу \texttt{typedef}: \\\\ \texttt{using mytype = int;} \\\\ \textbf{Alias template} позволяет написать собственный псевдоним с шаблонным параметром, например: \\ -\texttt{vector< T, mmap\_allocator > $\longmapsto$ my\_vector} \\ Синтаксис: \begin{minted} [frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template using my_vector = vector>; + +my_vector a; //vector> a; \end{minted} Как и \textbf{template variables}, \textbf{alias templates} используются для упрощения \texttt{std::type\_traits}. \\\\ -В C++14 все метафункции, возвращающие тип, имеют соответсвующий \textbf{alias template} с суффиксом \texttt{\_t} (\texttt{enable\_if\_t}).\\ +В C++14 все метафункции, возвращающие тип, имеют соответствующий \textbf{alias template} с суффиксом \texttt{\_t} (\texttt{enable\_if\_t}).\\ Позволяют сильно упростить код, так как пропадает необходимость писать \texttt{typename} и \texttt{::type}. -\subsection{Явное инстанцирование шаблонов. Подавление инстанцирования.} +\subsection{Явное инстанцирование шаблонов.} Явное инстанцирование шаблона позволяет сгенерировать код шаблонной функции или класса для конкретных параметров без использования его в коде. \\\\ Синтаксис: \begin{minted} [frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -283,8 +283,8 @@ \subsection{Явное инстанцирование шаблонов. Пода template class my_class; // for classes template double f(int, char); // for functions \end{minted} -При таком инстанцировании инстанцируются все не-template мемберы. \\\\ - +При таком инстанцировании инстанцируются все не-template мемберы. +\subsection{Подавление инстанцирования шаблонов.} В C++11 появилась возможность подавления неявного инстанцирования шаблона. \\\\ Синтаксис: \begin{minted} [frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -294,11 +294,10 @@ \subsection{Явное инстанцирование шаблонов. Пода Можно использовать это для уменьшения количества генерируемого кода. Например, можно подавить инстанцирование в header-файле и явно проинстанцировать в одном cpp-файле. \\ Так оптимизируется время компиляции. \section{std::type\_traits} -\texttt{std::type\_traits} - это библиотека, которая содержит множество полезных метафункции для работы с классами. В этой части будет проведен её обзор. \\\\ +\texttt{std::type\_traits} --- это библиотека, которая содержит множество полезных метафункции для работы с классами. В этой части будет проведен её обзор. \\\\ \subsection{Вспомогательные типы} \begin{itemize} \item \texttt{\textbf{integral\_constant} \\ Константа времени компиляции типа T со значением v. \\ - % TODO: очень важно, что is_same (integral_constant) содержит value. Содержит поле: \\ \texttt{\textbf{static constexpr T value = v;}} \item \texttt{\textbf{true\_type}} определен как integral\_constant @@ -312,7 +311,7 @@ \subsection{Вспомогательные типы} template struct is_same : std::true_type {}; \end{minted} -\subsection{Проверка на принадлежость типа к определенной категории} +\subsection{Проверка на принадлежность типа к определенной категории} \begin{table}[H] \caption{\label{tab:types} Категории типов} \begin{center} @@ -366,12 +365,12 @@ \subsection{Проерка типа на наличие определенных Проверяет наличие спецификатора volaitile или const volaitile. \item \textbf{\texttt{is\_signed}} \\\\ Проверяет, что если тип является арифметическим, то выражение T(-1) < T(0) = true. -То есть тип - знаковый. +То есть тип --- знаковый. \item \textbf{\texttt{is\_unsigned}} \\\\ Проверяет, что если тип является арифметическим, то выражение T(0) < T(-1) = true. -То есть тип - беззнаковый. +То есть тип --- беззнаковый. \item \texttt{\textbf{is\_trivially\_copyable}} \\\\ -Проверяет, что тип - \textit{тривиально копируемый}. \\\\ +Проверяет, что тип --- \textit{тривиально копируемый}. \\\\ Тип считается \textit{тривиально копируемым}, если: \begin{enumerate} \item Любой его конструктор копирования, move конструктор, копирующий оператор присваивания, @@ -381,13 +380,13 @@ \subsection{Проерка типа на наличие определенных \end{enumerate} Все типы, совместимые с C, тривиально копируемы. \\\\ Конструктор копирования, move конструктор, копирующий оператор присваивания, -move оператор присваивания (далее - операция) в классе T называется \textit{тривиальным}, если: +move оператор присваивания (далее --- операция) в классе T называется \textit{тривиальным}, если: \begin{enumerate} \item Он не определен пользователем (определен неявно, или помечен как default). \item T не имеет виртуальных функций. -\item T не имеет вирутальных баз. -\item Соответсвующая операция, выбирающаяся для прямой базы T, тривиальна. -\item Соответсвующая операция, выбирающаяся для каждого не статического класса-мембера, тривиальна. +\item T не имеет виртуальных баз. +\item Соответствующая операция, выбирающаяся для прямой базы T, тривиальна. +\item Соответствующая операция, выбирающаяся для каждого не статического класса-мембера, тривиальна. \end{enumerate} Конструктор/деструктор называется \textbf{\textit{тривиальным}}, если: \begin{enumerate} @@ -410,7 +409,7 @@ \subsection{Проерка типа на наличие определенных Имеет аргументы: template \begin{enumerate} \item Проверяет, что выражение \texttt{T obj(std::declval()}, вызывающее констурктор от типа корректно. -\item Проверяет (1) и что вызов конструктора не вызвыает \textit{не тривиальных} операций. +\item Проверяет (1) и что вызов конструктора не вызывает \textit{не тривиальных} операций. \item Проверяет (1) и что вызов конструктора noexcept. \end{enumerate} Есть аналогичные метафункции, проверяющие \texttt{default\_constructible, copy\_constructible, move\_constructible}. Они выражаются через соответствующие версии \texttt{is\_constructible}. @@ -420,8 +419,8 @@ \subsection{Проерка типа на наличие определенных Имеет аргументы: template \begin{enumerate} \item Проверяет, что выражение \texttt{std::declval() = std::declval()}, вызывающее оператор присваивания корректно. -\item Проверяет (1) и что вызов конструктора не вызвыает \textit{не тривиальных} операций. -\item Проверяет (1) и что вызов конструктора noexcept +\item Проверяет (1) и что вызов конструктора не вызывает \textit{не тривиальных} операций. +\item Проверяет (1) и что вызов конструктора noexcept. \end{enumerate} Есть аналогичные метафункции, проверяющие \texttt{copy\_assignable, move\_assignable}. Они выражаются через соответствующие версии \texttt{is\_assignable}. \\\\ \item Абсолютно аналогично строится тройка метафункций @@ -430,9 +429,9 @@ \subsection{Проерка типа на наличие определенных \end{itemize} \subsection{Взаимоотношения типов} \begin{itemize} - \item \textbf{\texttt{is\_same}} - проверяет, что типы T и U совпадают. - \item \textbf{\texttt{is\_base\_of}} - проверяет, что Derived наследуется от Base или они совпадают. - \item \textbf{\texttt{is\_convertible}} - проверяет корректность следующего определения функции: + \item \textbf{\texttt{is\_same}} --- проверяет, что типы T и U совпадают. + \item \textbf{\texttt{is\_base\_of}} --- проверяет, что Derived наследуется от Base или они совпадают. + \item \textbf{\texttt{is\_convertible}} --- проверяет корректность следующего определения функции: \\\\ \texttt{To test() \{ return std::declval(); \}}\\\\ что обеспечивает возможность неявного конвертирования From в To. \end{itemize} @@ -450,4 +449,4 @@ \subsection{Трансформирование типов} \item \texttt{\textbf{add\_rvalue\_reference}} \item \texttt{\textbf{remove\_pointer}} \item \texttt{\textbf{add\_pointer}} -\end{itemize} +\end{itemize} \ No newline at end of file From f30035b2608a7c533f1382e4a9debaa1c6cda59a Mon Sep 17 00:00:00 2001 From: DrDet Date: Wed, 10 Jan 2018 22:17:38 +0300 Subject: [PATCH 7/9] small fix --- metaprogramming.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metaprogramming.tex b/metaprogramming.tex index 8d3cd2d..bed56a9 100644 --- a/metaprogramming.tex +++ b/metaprogramming.tex @@ -356,7 +356,7 @@ \subsection{Проверка на принадлежность типа к оп \texttt{bool, char, char16\_t, char32\_t, wchar\_t, short, int, long, long long}. \\\\ \textbf{type\_traits} предоставляет набор метафункций, позволяющих проверить, принадлежит ли тип T к какой-либо категории из приведенных в таблице выше. \\\\ Название таких метафункций имеет вид: \texttt{is\_/*название\_категории*/}. -\subsection{Проерка типа на наличие определенных свойств} +\subsection{Проверка типа на наличие определенных свойств} В C++11 поддерживается широкий набор метафункций для проверки наличия у типа определенных свойств. Рассмотрим основные из них: \begin{itemize} \item \textbf{\texttt{is\_const}} \\\\ From 72fc52a7f580e73236d55ac8316fb39b5fa745dd Mon Sep 17 00:00:00 2001 From: DrDet Date: Fri, 19 Jan 2018 23:57:23 +0300 Subject: [PATCH 8/9] one more small fix --- metaprogramming.tex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/metaprogramming.tex b/metaprogramming.tex index bed56a9..4e29c86 100644 --- a/metaprogramming.tex +++ b/metaprogramming.tex @@ -213,13 +213,13 @@ \subsection{If constexpr} Полностью корректная версия должна содержать SFINAE-проверку: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template -decltype(enable_if::value>::type, T) abs(T value) +enable_if::value, T>::type abs(T value) { return (value < 0 ? -value : value); } template -decltype(enable_if::value>::type, T) abs(T value) +enable_if::value, T>::type abs(T value) { return value; } @@ -251,7 +251,7 @@ \subsection{Template variables} Рассмотрим на примере \texttt{is\_same}: \begin{minted} [linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template -constexpr bool is_same_v = is_same::value; +constexpr bool is_same_v = is_same::value; \end{minted} Теперь вместо \texttt{is\_same::value} можем писать просто \texttt{is\_same\_v}. \\\\ В C++14 все метафункции, возвращающие значение, имеют соответствующие шаблонные переменные с суффиксом \texttt{\_v}. From 7377cfd4b0514969dd1144cb52b6100e955929e0 Mon Sep 17 00:00:00 2001 From: DrDet Date: Sat, 20 Jan 2018 00:14:43 +0300 Subject: [PATCH 9/9] fix typo --- metaprogramming.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metaprogramming.tex b/metaprogramming.tex index 4e29c86..7c84a1d 100644 --- a/metaprogramming.tex +++ b/metaprogramming.tex @@ -243,7 +243,7 @@ \subsection{Template variables} template constexpr T pi = T(3.14159265); -constexpr float x = pi; +constexpr double x = pi; constexpr float y = pi; \end{minted} Шаблонные переменные обязаны быть \textbf{constexpr}. Допускаются специализации. \\\\