Главная страница
Навигация по странице:

  • Упражнение 16.32. Что происходит при дедукции аргумента шаблона

  • Язык программирования C Пятое издание


    Скачать 1.85 Mb.
    НазваниеЯзык программирования C Пятое издание
    Дата15.07.2019
    Размер1.85 Mb.
    Формат файлаpdf
    Имя файла620354-www.libfox.ru.pdf
    ТипДокументы
    #84130
    страница45 из 54
    1   ...   41   42   43   44   45   46   47   48   ...   54
    Можно ли явно создать экземпляр vector<NoDefault>? Если нет, то почему?
    Page 849/1103

    Упражнение 16.27. Объясните по каждому помеченному оператору, происходит ли создание экземпляра. Если создается экземпляр шаблона, объясните, почему; если нет, то тоже почему. template <typename Т> class Stack { }; void f1(Stack<char>); // (a) class Exercise {
    Stack<double> &rsd; // (b)
    Stack<int> si; // (c)
    }; int main() {
    Stack<char> *sc; // (d) f1(*sc); // (e) int iObj = sizeof(Stack<string>); // (f)
    }
    16.1.6. Эффективность и гибкость
    Библиотечные типы интеллектуальных указателей (см. раздел 12.1) являются хорошим примером грамотно спроектированных шаблонов.
    Очевидное различие между указателями shared_ptr и unique_ptr в стратегии, которую они используют для управления содержащимися в них указателями: один класс предоставляет совместную собственность; а другой — единоличною собственность на хранимый указатель.
    Это различие и является основанием для создания данных классов.
    Данные классы отличаются также тем, как они позволяют пользователям переопределять свою стандартную функцию удаления. Для переопределения функции удаления класса shared_ptr достаточно предоставить ему при создании вызываемый объект или функцию reset(). У объекта класса unique_ptr, напротив, тип функции удаления является частью типа.
    При определении указателя unique_ptr пользователи должны предоставлять этот тип как явный аргумент шаблона. В результате для указателя unique_ptr сложней предоставить собственную функцию удаления.
    Различие в способе работы функции удаления — это лишь частность функциональных возможностей данных классов. Но, как будет вскоре продемонстрировано, это различие в стратегии реализации может серьезно повлиять на производительность. Привязка функции удаления во время выполнения
    Даже не зная, как именно реализуются библиотечные типы, вполне можно догадаться, что указатель shared_ptr обращается к своей функции удаления косвенно. Поэтому функция удаления должна храниться как указатель или как класс (такой как function из раздела 14.8.3),
    инкапсулирующий указатель.
    Page 850/1103

    То, что тип функции удаления не известен до времени выполнения, позволяет убедиться, что класс shared_ptr не содержит функцию удаления как непосредственный член класса.
    Действительно, класс shared_ptr позволяет изменить тип функции удаления на протяжении продолжительности его существования. Вполне можно создать указатель shared_ptr,
    используя функцию удаления одного типа, а впоследствии использовать функцию reset(),
    чтобы использовать для того же указателя shared_ptr другой тип функции удаления. Вообще,
    у класса не может быть члена, тип которого изменяется во время выполнения.
    Следовательно, функция удаления должна храниться отдельно.
    Размышляя о том, как должна работать функция удаления, предположим, что класс shared_ptr хранит контролируемый указатель в переменной-члене класса по имени p, а обращение к функции удаления осуществляется через член класса по имени del. Деструктор класса shared_ptr должен включать такой оператор:
    // значение del станет известно только во время выполнения; вызов
    // через указатель del ? del(p) : delete p; // вызов del (p) требует перехода во время
    // выполнения к области хранения del
    Поскольку функция удаления хранится отдельно, вызов del(p) требует перехода во время выполнения к области хранения del и выполнения кода, на который он указывает. Привязка функции удаления во время компиляции
    Теперь давайте подумаем, как мог бы работать класс unique_ptr. В этом классе тип функции удаления является частью типа unique_ptr. Таким образом, у шаблона unique_ptr есть два параметра шаблона: представляющий контролируемый указатель и представляющий тип функции удаления. Поскольку тип функции удаления является частью типа unique_ptr, тип функции-члена удаления известен на момент компиляции. Функция удаления может храниться непосредственно в каждом объекте класса unique_ptr.
    Деструктор класса unique_ptr работает подобно таковому у класса shared_ptr, в котором он вызывает предоставленную пользователем функцию удаления или выполняет оператор delete для хранимого указателя:
    // del связывается во время компиляции; создается экземпляр прямого
    // вызова функции удаления del(p); // нет дополнительных затрат во время выполнения
    Тип del — это либо заданный по умолчанию тип функции удаления, либо тип,
    Page 851/1103
    предоставленный пользователем. Это не имеет значения; так или иначе, выполняемый код будет известен во время компиляции. Действительно, если функция удаления похожа на класс DebugDelete (см. раздел 16.1.4), этот вызов мог бы даже быть встраиваемым во время компиляции.
    При привязке функции удаления во время компиляции класс unique_ptr избегает во время выполнения дополнительных затрат на косвенный вызов своей функции удаления. При привязке функции удаления во время выполнения класс shared_ptr облегчает пользователю переопределение функции удаления. Упражнения раздела 16.1.6
    Упражнение 16.28. Напишите собственные версии классов shared_ptr и unique_ptr.
    Упражнение 16.29. Пересмотрите свой класс Blob так, чтобы использовать собственную версию класса shared_ptr, а не библиотечную.
    Упражнение 16.30. Повторно выполните некоторые из своих предыдущих программ, чтобы проверить собственные переделанные классы shared_ptr и Blob. (Примечание: реализация типа weak_ptr не рассматривается в этом издании, поэтому не получится использовать класс
    BlobPtr с пересмотренным классом Blob.)
    Упражнение 16.31. Объясните, как компилятор мог бы встроить вызов функции удаления,
    если бы с классом unique_ptr был использован класс DebugDelete.
    16.2. Дедукция аргумента шаблона
    Как уже упоминалось, для определения параметров шаблона для шаблона функции компилятор по умолчанию использует аргументы в вызове. Процесс определения аргументов шаблона по аргументам функции называется дедукцией аргумента шаблона (template argument deduction). В ходе дедукции аргумента шаблона компилятор использует типы аргументов вызова для поиска таких аргументов шаблона, которые обеспечат лучшее соответствие создаваемой версии функции для данного вызова.
    16.2.1. Преобразования и параметры типа шаблона
    Подобно нешаблонным функциям, передаваемые в вызове шаблона функции аргументы используются для инициализации параметров этой функции. Параметры функции, тип которых использует параметр типа шаблона, имеют специальные правила инициализации.
    Только очень ограниченное количество автоматических преобразований применимо к таким аргументам. Вместо преобразования аргументов компилятор создает новые экземпляры.
    Как обычно, спецификаторы const верхнего уровня (см. раздел 2.4.3) в параметре или аргументе игнорируются. Единственными остальными преобразованиями, выполняемыми при вызове шаблона функции, являются следующие.
    • Преобразования констант: параметр функции, являющийся ссылкой (или указателем) на константу, может быть передан как ссылка (или указатель) на не константный объект (см.
    раздел 4.11.2).
    • Преобразование массива или функции в указатель: если тип параметра функции не будет
    Page 852/1103
    ссылочным, то к аргументам типа массива или функции будет применено обычное преобразование указателя. Аргумент типа массива будет преобразован в указатель на его первый элемент. Точно так же аргумент типа функции будет преобразован в указатель на тип функции (см. раздел 4.11.2).
    Другие преобразования, такие как арифметические преобразования (см. раздел 4.11.1),
    преобразования производного в базовый (см. раздел 15.2.2) и пользовательские преобразования (см. разделы 7.5.4 и 14.9) не выполняются.
    В качестве примера рассмотрим вызовы функции fobj() и fref(). Функция fobj() копирует свои параметры, тогда как параметры функции fref() являются ссылками: template <typename Т> Т fobj(Т, Т); // аргументы копируются template <typename Т> Т fref(const Т&, const Т&); // ссылки string s1("a value"); const string s2("another value"); fobj(s1, s2); // вызов fobj(string, string); const игнорируется fref(s1, s2); // вызов fref(const strings, const string&) использует
    // допустимое преобразования в константу для s1 int а[10], b[42]; fobj(a, b); // вызов f(int*, int*) fref(a, b); // ошибка: типы массивов не совпадают
    В первой паре вызовов как аргументы передаются строка и константная строка. Даже при том, что эти типы не соответствуют точно друг другу, оба вызова допустимы. В вызове функции fobj() аргументы копируются, поэтому не имеет значения, был ли первоначальный объект константой. В вызове функции fref() тип параметра — ссылка на константу.
    Преобразование в константу для ссылочного параметра является разрешенным преобразованием, поэтому данный вызов допустим.
    В следующей паре вызовов как аргументы передаются массивы, отличающиеся размером, а следовательно, имеющие разные типы. В вызове функции fobj() различие типов массивов не имеет значения. Оба массива преобразуются в указатели. Типом параметра шаблона в функции fobj является int*. Вызов функции fref(), однако, недопустим. Когда параметр является ссылкой, массивы не преобразовываются в указатели (см. раздел 6.2.4). Типы а и b
    Page 853/1103
    не совпадают, поэтому вызов ошибочен.
    Единственными допустимыми автоматическими преобразованиями для аргументов в параметры типа шаблонов являются преобразования константы в массив или функций в указатель. Параметры функций с одинаковым типом параметра шаблона
    Параметр типа шаблона применим как тип нескольких параметров функции. Поскольку набор преобразований ограничен, аргументы таких параметров должны быть, по существу, того же типа. Если выведенные типы не совпадают, то вызов ошибочен. Например, функция compare() (см. раздел 16.1.1) получает два параметра const Т&. У ее аргументов должен быть фактически тот же тип: long lng; compare(lng, 1024); // ошибка: нельзя создать
    // экземпляр compare(long, int)
    Этот вызов ошибочен потому, что у аргументов функции compare() не совпадают типы. Для первого аргумента выведен аргумент шаблона типа long; а для второго — int. Эти типы не совпадают, поэтому дедукция аргумента шаблона терпит неудачу.
    Если необходимо обеспечить обычные преобразования аргументов, можно определить функцию с двумя параметрами типа:
    // типы аргумента могут отличаться, но должны быть совместимы template <typename A, typename B> int flexibleCompare(const A& v1, const B& v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0;
    }
    Теперь пользователь может предоставлять аргументы разных типов: long lng; flexibleCompare(lng, 1024); // ok: вызов flexibleCompare(long, int)
    Конечно, должен существовать оператор <, способный сравнивать значения этих типов.
    Обычные преобразования применимы к обычным аргументам
    У шаблона функции могут быть параметры, определенные с использованием обычных типов,
    т.е. типов, которые не задействуют параметр типа шаблона. Такие аргументы не
    Page 854/1103
    обрабатываются специальным образом; они преобразуются, как обычно, в соответствующий тип параметра (см. раздел 6.1). Рассмотрим, например, следующий шаблон: template <typename Т> ostream &print(ostream &os, const T &obj) { return os << obj;
    }
    Тип первого параметра функции известен: ostream&. У второго параметра, obj, тип параметра шаблона. Поскольку тип параметра os фиксирован, при вызове функции print() к переданным ему аргументам применимы обычные преобразования: print(cout, 42); // создает экземпляр print(ostream&, int) ofstream f("output"); print(f, 10); // использует print(ostream&, int);
    // преобразует f в ostream&
    В первом вызове тип первого аргумента точно соответствует типу первого параметра. Этот вызов задействует ту версию функции print(), которая получает тип ostream& и тип int для создания экземпляра. Во втором вызове первый аргумент имеет тип ofstream, а преобразование из ofstream в ostream& допустимо (см. раздел 8.2.1). Поскольку тип этого параметра не зависит от параметра шаблона, компилятор неявно преобразует f в ostream&.
    Обычные преобразования применимы к аргументам, тип которых не является параметром шаблона. Упражнения раздела 16.2.1

    Упражнение 16.32. Что происходит при дедукции аргумента шаблона?
    Упражнение 16.33. Назовите два преобразования типов, допустимых для аргументов функций, при дедукции аргумента шаблона.
    Упражнение 16.34. С учетом только следующего кода объясните, допустим ли каждый из этих вызовов. Если да, то каков тип Т? Если нет, то почему? template <class Т> int compare(const T&, const T&);
    (a) compare("hi", "world"); (b) compare("bye", "dad");
    Упражнение 16.35. Какой из следующих вызовов ошибочен (если он есть)? Каков тип Т
    допустимых вызовов? В чем проблема недопустимых вызовов? template <typename Т> Т calc(T, int); template <typename Т> Т fcn(Т, Т); double d; float f; char с;
    Page 855/1103

    (a) calc(с, 'c'); (b) calc(d, f);
    (c) fcn(c, 'c'); (d) fcn(d, f);
    Упражнение 16.36. Что происходит при следующих вызовах: template <typename Т> f1(Т, Т); template <typename T1, typename T2) f2(T1, T2); int i = 0, j = 42, *p1 = &i, *p2 = &j; const int *cp1 = &i, *cp2 = &j;
    (a) f1(p1, p2); (b) f2(p1, p2); (c) f1(cp1, cp2);
    (d) f2(cp1, cp2); (e) f1(p1, cp1); (e) f2(p1, cp1);
    16.2.2. Явные аргументы шаблона функции
    В некоторых редких случаях компилятор не может вывести типы аргументов шаблона. В
    других случаях следует позволить пользователю контролировать создание экземпляра шаблона. Оба эти случая наиболее вероятны тогда, когда тип возвращаемого значения функции отличается от типов используемых ею параметров. Определение явного аргумента шаблона
    В качестве примера случая, когда необходимо позволить пользователю задавать тип,
    определим шаблон функции sum(), получающий аргументы двух разных типов. Тип результата будет определять пользователь. Таким образом, пользователь сможет выбрать необходимую ему точность.
    Чтобы предоставить пользователю контроль над типом возвращаемого значения, определим третий параметр шаблона, представляющий тип возвращаемого значения:
    // тип T1 не может быть выведен: он отсутствует в списке параметров
    // функции template <typename T1, typename T2, typename T3>
    T1 sum(T2, T3);
    В данном случае нет никакого аргумента, тип которого мог бы использоваться для выведения типа T1. Для этого параметра при каждом вызове функции sum() вызывающая сторона должна предоставить явный аргумент шаблона (explicit template argument).
    Явный аргумент шаблона предоставляется вызову тем же способом, что и экземпляру шаблона класса. Явные аргументы шаблона определяются в угловых скобках после имени функции и перед списком аргументов:
    //
    Page 856/1103

    T1 определяется явно; T2 и T3 выводятся из типов аргумента auto val3 = sum<long long>(i, lng); // long long sum(int, long)
    Этот вызов явно определяет тип параметра T1. Компилятор выведет типы для параметров T2
    и T3 из типов переменных i и lng.
    Явные аргументы шаблона отвечают соответствующим параметрам шаблона слева направо;
    первый аргумент шаблона отвечает первому параметру шаблона, второй аргумент —
    второму параметру и т.д. Явный аргумент шаблона может быть пропущен только для замыкающих (крайних справа) параметров, и то, только если они могут быть выведены из параметров функции. Если функция sum() была написана следующим образом:
    // плохой проект: пользователи вынуждены явно определять все три
    // параметра шаблона template <typename T1, typename T2, typename T3>
    T3 alternative_sum(T2, T1); то пользователям придется всегда определять аргументы для всех трех параметров:
    // ошибка: нельзя вывести начальные параметры шаблона auto val3 = alternative_sum<long long>(i, lng);
    // ok: все три параметра определяются явно auto val2 = alternative_sum<long long, int, long>(i, lng); Нормальные преобразования применимы к аргументам, определенным явно
    По тем же причинам, по которым нормальные преобразования разрешены для параметров,
    определенных с использованием обычных типов (см. раздел 16.2.1), нормальные преобразования применимы также для аргументов, параметры типа шаблона которых определяются явно: long lng; compare(lng, 1024); // ошибка: параметры шаблона не совпадают compare<long>(lng, 1024); // ok: создает экземпляр compare(long, long) compare<int>(lng, 1024); //
    Page 857/1103
    ok: создает экземпляр compare(int, int)
    Как уже упоминалось, первый вызов ошибочен, поскольку у аргументов функции compare()
    должен быть одинаковый тип. Если тип параметра шаблона определен явно, обычные преобразования вполне применимы. Таким образом, вызов compare<long>()
    эквивалентен вызову функции, получающей два параметра const long&. Параметр типа int автоматически преобразуется в тип long. Во втором вызове параметр Т явно определяется как тип int, таким образом, тип аргумента lng преобразовывается в int. Упражнения раздела
    16.2.2
    Упражнение 16.37. Библиотечная функция max() имеет два параметра функции и возвращает больший из своих аргументов. У этой функции есть один параметр типа шаблона. Можно ли вызвать функцию max(), передав ей аргументы типа int и double? Если да, то как? Если нет, то почему?
    Упражнение 16.38. Когда происходит вызов функции make_shared() (см. раздел 12.1.1),
    следует предоставить явный аргумент шаблона. Объясните, почему этот аргумент необходим и как он используется.
    Упражнение 16.39. Используйте явный аргумент шаблона, чтобы сделать возможной передачу двух строковых литералов первоначальной версии функции compare() из раздела
    16.1.1.
    16.2.3. Замыкающие типы возвращаемого значения и трансформация типа
    Применение явного аргумента шаблона для представления типа возвращаемого значения шаблона функции хорошо работает тогда, когда необходимо позволить пользователю определять тип возвращаемого значения. В других случаях обязательное предоставление явного аргумента шаблона налагает дополнительное бремя на пользователя без всяких преимуществ. Например, можно написать функцию, которая получает два обозначающих последовательность итератора и возвращает ссылку на элемент этой последовательности: template <typename It>
    ??? & fcn(It beg, It end) {
    // обработка диапазона return *beg; // возвратить ссылку на элемент из диапазона
    }
    Точный тип, подлежащий возвращению, неизвестен, но известно, что он будет ссылкой на тип элемента обрабатываемой последовательности: vector<int> vi = {1,2,3,4,5};
    Blob<string> ca = { "hi", "bye" }; auto &i = fcn(vi.begin(), vi.end()); //
    Page 858/1103
    fcn() должна возвратить int& auto &s = fcn(ca.begin(), ca.end()); // fcn() должна возвратить string&
    Здесь известно, что функция возвратит ссылку *beg, а также, что можно использовать выражение decltype(*beg) для получения типа этого выражения. Однако параметр beg не существует, пока не встретится список параметров. Чтобы определить эту функцию, следует использовать замыкающий тип возвращаемого значения (см. раздел 6.3.3). Поскольку замыкающий тип располагается после списка параметров, он может использовать параметры функции:
    // замыкающий тип позволяет объявлять тип возвращаемого значения уже
    // после списка параметров template <typename It> auto fcn(It beg, It end) -> decltype(*beg) {
    // обработка диапазона return *beg; // возвратить ссылку на элемент из диапазона
    }
    Здесь компилятору было указано, что тип возвращаемого значения функции fcn() совпадает с типом, возвращенным при обращении к значению параметра beg. Оператор обращения к значению возвращает l-значение (см. раздел 4.1.1), таким образом, выведенный выражением decltype тип является ссылкой на тип элемента, обозначаемого параметром beg.
    Следовательно, если функция fcn() будет вызвана для последовательности строк, то типом возвращаемого значения будет string&. Если это будет последовательность элементов типа int, то возвращен будет тип int&. Трансформация типа классов библиотечных шаблонов
    Иногда прямого доступа к необходимому типу нет. Например, может возникнуть необходимость в функции, подобной fcn(), которая возвращает элемент по значению (см.
    раздел 6.3.2), а не по ссылке.
    Проблема написания этой функции в том, что о передаваемых типах неизвестно почти ничего. Единственные известные в этой функции операции, которые можно использовать, —
    это операции с итераторами, и нет никаких операций с итераторами, которые возвращают элементы (в противоположность ссылкам на элементы).
    Чтобы получить тип элемента, можно использовать библиотечный шаблон трансформации типа (type transformation). Эти шаблоны определяются в заголовке type_traits.
    Обычно классы заголовка type_traits используются для так называемого шаблонного метапрограммирования, не рассматриваемого в данной книге. Однако шаблоны
    Page 859/1103
    трансформации типа полезны и в обычном программировании. Они описаны в табл. 16.1, а их реализация рассматривается в разделе 16.5 (стр. 892).
    В данном случае для получения типа элемента можно использовать шаблон remove_reference. У шаблона remove_reference один параметр типа шаблона и (открытый)
    тип-член type. Если экземпляр шаблона remove_reference создается со ссылочным типом, то тип type будет ссылочным. Например, если создать экземпляр remove_reference<int&>, то типом type будет int. Точно так же, если создать экземпляр remove_reference<string&>, то типом type будет string и т.д. Таким образом, при условии, что beg — итератор, следующее выражение возвратит тип элемента,
    на который указывает итератор beg: remove_reference<decltype(*beg)>::type
    Выражение decltype(*beg) возвратит ссылочный тип элемента type. Выражение remove_reference::type удаляет ссылку, оставляя тип самого элемента.
    Таблица 16.1. Стандартные шаблоны трансформации типа Для
    Mod <Т>, где
    Mod есть Если T есть To
    Mod <Т>::type есть remove_reference X& или X&& X в противном случае T
    add_const X&, const X или функция T в противном случае const Т add_l-value_reference
    X& T X&& X& в противном случае T& add_r-value reference X& или
    X&& T в противном случае Т&& remove_pointer X* X в противном случае T
    add_pointer X& или X&& X* в противном случае T* make_signed unsigned X X в противном случае T make_unsigned знаковый тип unsigned Т в противном случае Т
    remove_extent X[n] X в противном случае T remove_all_extents X[n1][n2]... X в противном случае T
    Используя шаблон remove_reference и замыкающий тип с выражением decltype, можно написать собственную функцию, возвращающую копию значения элемента:
    // для использования типа-члена параметра шаблона следует
    // использовать typename; см. p. 16.1.3 template <typename It> auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type {
    // обработка диапазона return *beg; // возвратить копию элемента из диапазона
    }
    Page 860/1103

    Обратите внимание, что тип-член type зависит от параметра шаблона. Таким образом, чтобы указать компилятору, что type представляет тип (см. раздел 16.1.3), в объявлении типа возвращаемого значения следует использовать ключевое слово typename.
    Каждый из описанных в табл. 16.1 шаблонов трансформации типа работает так же, как шаблон remove_reference. У каждого шаблона есть открытый член type, представляющий тип.
    Этот тип может быть связан с собственным параметром типа шаблона способом, о котором свидетельствует имя шаблона. Если невозможно (или ненужно) преобразовать параметр шаблона, тип-член type имеет тип параметра самого шаблона. Например, если Т — это тип указателя, то remove_pointer<T>::type возвращает тип, на который указывает указатель T.
    Если T не указатель, то никакого преобразования не нужно. В данном случае у типа type тот же тип, что и у Т. Упражнения раздела 16.2.3
    Упражнение 16.40. Корректна ли следующая функция? Если нет, то почему? Если она допустима, то каковы ограничения на типы ее аргументов (если они есть) и каков тип возвращаемого значения? template <typename It> auto fcn3(It beg, It end) -> decltype(*beg + 0) {
    // обработка диапазона return *beg; // возвратить копию элемента из диапазона
    }
    Упражнение 16.41. Напишите версию функции sum() с типом возвращаемого значения,
    который будет гарантированно большим, чтобы содержать результат сложения.
    16.2.4. Указатели на функцию и дедукция аргумента
    При инициализации или присвоении указателя на функцию (см. раздел 6.7) из шаблона функции для вывода аргументов шаблона компилятор использует тип указателя.
    Предположим, например, что есть указатель на функцию, которая возвращает тип int и получает два параметра, каждый из которых является ссылкой на const int. Этот указатель можно использовать для указания на экземпляр функции compare(): template <typename Т> int compare(const T&, const T&);
    // pf1 указывает на экземпляр int compare(const int&, const int&) int (*pf1)(const int&, const int&) = compare;
    Тип параметров pf1 определяет тип аргумента шаблона для параметра Т. Аргументом шаблона для параметра Т будет int. Указатель pf1 указывает на экземпляр функции compare()
    с параметром Т, связанным с типом int. Если аргументы шаблона не могут быть выведены из
    Page 861/1103
    типа указателя функции, произойдет ошибка:
    // перегруженные версии func(); каждая получает разный тип указателя
    // функции void func(int(*)(const string&, const string&)); void func(int(*)(const int&, const int&)); func(compare); // ошибка: какой из экземпляров compare?
    Проблема в том, что, глядя на тип параметра функции func(), невозможно определить уникальный тип для аргумента шаблона. Вызов функции func() мог бы создать экземпляр версии функции compare(), получающей целые числа или версию, получающую строки.
    Поскольку невозможно идентифицировать уникальный экземпляр для аргумента функции func(), этот вызов не будет откомпилирован.
    Неоднозначность вызова функции func() можно устранить при помощи явных аргументов шаблона:
    // ok: явно определенная версия экземпляра compare() func(compare<int>); // передача compare(const int&, const int&)
    Это выражение вызывает версию функции func(), получающую указатель на функцию с двумя параметрами типа const int&.
    Когда возвращается адрес экземпляра шаблона функции, контекст должен позволять однозначно идентифицировать тип или значение для каждого параметра шаблона.
    16.2.5. Дедукция аргумента шаблона и ссылки
    Чтобы лучше понять дедукцию типа, рассмотрим такой вызов функции где параметр функции p является ссылкой на параметр типа шаблона T: template <typename Т> void f(Т &p);
    Обратите внимание на два момента: здесь применяются обычные правила привязки ссылок;
    и спецификаторы const здесь нижнего уровня, а не верхнего. Дедукция типа из параметров ссылки на l-значения функций
    Когда параметр функции представляет собой обычную ссылку (l-значение) на параметр типа шаблона (т.е. имеющего форму T&), правила привязки гласят, что передавать можно
    Page 862/1103
    только l-значения (например, переменная или выражение, возвращающее ссылочный тип).
    Этот аргумент может быть или не быть константным. Если аргумент будет константой, то тип
    Т будет выведен как константный: template <typename Т> void f1(Т&); // аргумент должен быть l-значением
    // вызовы f1() используют ссылочный тип аргумента как тип параметра
    // шаблона f1(i); // i - это int; параметр шаблона Т - это int f1(ci); // ci - это const int; параметр шаблона Т - это const int f1(5); // ошибка: аргумент ссылочного параметра
    // должен быть l-значением
    Если параметр функции имеет тип const Т&, обычные правила привязки гласят, что можно передать любой вид аргумента — объект (константный или нет), временный объект или литеральное значение. Когда сам параметр функции является константой, выведенный для параметра Т тип не будет константным типом. Константность является частью типа параметра функции, и поэтому она не становится также частью типа параметра шаблона: template <typename Т> void f2(const T&); // может получать r-значения
    // параметр в f2() - это const &; const в аргументе неуместен
    // в каждом из этих трех вызовов параметр функции f2() выводится
    // как const int& f2(i); // i - это int; параметр шаблона Т - это int f2(ci); //
    Page 863/1103
    ci - это const int, но параметр шаблона T - это int f2(5); // параметр const & может быть привязан к r-значению;
    //
    Т - это int Дедукция типа из параметров ссылки на r-значения функций
    Когда параметр функции является ссылкой на r-значение (см. раздел 13.6.1), т.е. имеет форму Т&&, обычные правила привязки гласят, что этому параметру можно передать r-значение. При этом дедукция типа ведет себя таким же образом, как дедукция обычного ссылочного параметра функции на l-значение. Выведенный тип для параметра Т —
    это тип r-значения: template <typename Т> void f3(T&&); f3(42); // аргумент - это r-значение типа int; параметр
    // шаблона Т - это int Сворачивание ссылок и параметры ссылок на r-значения
    Предположим, что i является объектом типа int. Можно подумать, что такой вызов, как f3(i),
    будет недопустим. В конце концов, i — это l-значение, а ссылку на r-значение обычно нельзя связать с l-значением. Однако язык определяет два исключения из обычных правил привязки,
    которые позволяют это. На этих исключениях из правил основан принцип работы таких библиотечных функций, как move().
    Первое исключение относится к дедукции типа для ссылочного параметра на r-значение.
    Когда l-значение (например, i) передается параметру функции, являющемуся ссылкой на r-значение на параметр типа шаблона (например, Т&&), компилятор выводит параметр типа шаблона как тип ссылки на l-значение аргумента. Поэтому, когда происходит вызов f3(i), компилятор выводит тип Т как int&, а не int.
    Выведение типа Т как int&, казалось бы, означает, что параметр функции f3() будет ссылкой на r-значение типа int&. Обычно нельзя (непосредственно) определить ссылку на ссылку (см. раздел 2.3.1). Но это можно сделать косвенно, через псевдоним типа (см.
    раздел 2.5.1) или через параметр типа шаблона.
    В таких ситуациях проявляется второе исключение из обычных правил привязки: если косвенно создать ссылку на ссылку, то эти ссылки " сворачиваются " (collapse). Во всех случаях кроме одного сворачивание ссылок формирует обычный тип ссылки на l-значение. Новый стандарт дополняет правила свертывания,
    включая ссылки на r-значение. Ссылки сворачиваются, формируя ссылку на r-значение только в специфическом случае ссылки на r-значение на ссылку на r-значение. Таким образом, для данного типа X:
    • X& &, X& && и X&& & сворачиваются в тип X&.
    • Тип X&& && сворачивается в тип X&&.
    Сворачивание ссылок применимо только тогда, когда ссылка на ссылку создается косвенно,
    Page 864/1103
    как в псевдониме типа или параметре шаблона.
    Комбинация правил свертывания ссылок и специального правила дедукции типа для ссылочных на r-значения параметров означает, что можно вызвать функцию f3() для l-значения. Когда параметру функции f3() (ссылке на r-значение) передается l-значение,
    компилятор выведет тип T как тип ссылки на l-значение: f3(i); // аргумент - это l-значение; параметр Т шаблона - это int& f3(ci); // аргумент - это l-значение;
    // параметр Т шаблона - это const int&
    Когда параметр T шаблона выводится как ссылочный тип, правило свертывания гласит, что параметр функции T&& сворачивается в тип ссылки на l-значение. Например,
    результирующий экземпляр для вызова f3(i) получится примерно таким:
    // недопустимый код, приведен только для примера void f3<int&>(int& &&); // когда T - это int&, параметр
    // функции - это int& &&
    Параметр функции f3() — это Т&&, а T — это int&, таким образом, Т&&
    будет int& &&, что сворачивается в int&. Таким образом, даже при том, что формой параметра функции f3() будет ссылка на r-значение (т.е. T&&), этот вызов создаст экземпляр функции f3() с типом ссылки на l-значение (т.е. int&): void f3<int&>(int&); // когда T - это int&, параметр функции
    // сворачивается в int&
    У этих правил есть два важных следствия.
    • Параметр функции, являющийся ссылкой на r-значение для параметра типа шаблона
    (например, Т&&), может быть связан с l-значением.
    • Если аргумент будет l-значением, то выведенный тип аргумента шаблона будет типом ссылки на l-значение, и экземпляр параметра функции будет создан как (обычный) параметр ссылки на l-значение (Т&).
    Стоит также обратить внимание на то, что параметру функции Т&& косвенно можно
    Page 865/1103
    передать аргумент любого типа. Параметр такого типа может использоваться с r-значениями,
    а, как было продемонстрировано только что, также и с l-значениями.
    Параметру функции, являющемуся ссылкой на r-значение на тип параметра шаблона (т.е.
    Т&&), может быть передан аргумент любого типа. Когда такому параметру передается l-значение, экземпляр параметра функции создается как обычная ссылка на l-значение (T&). Шаблоны функций с параметрами ссылки на r-значения
    У того факта, что параметр шаблона может быть выведен как ссылочный тип, имеются удивительные последствия для кода в шаблоне: template <typename Т> void f3(Т&& val) {
    T t = val; // копировать или привязать ссылку? t = fcn(t); // изменит ли присвоение только t или val и t? if (val == t) { /* ... */ } // всегда истинно, если Т - ссылочный тип
    }
    Когда вызов функции f3() происходит для такого r-значения, как литерал 42, T имеет тип int. В
    данном случае локальная переменная t имеет тип int и инициализируется при копировании значения параметра val. При присвоении переменной t параметр val остается неизменным.
    С другой стороны, когда происходит вызов функции f3() для l-значения i, типом T будет int&. Когда определяется и инициализируется локальная переменная t, у нее будет тип int&. Инициализация переменной t свяжет ее с параметром val. При присвоении переменной t одновременно изменяется и параметр val. В этом экземпляре функции f3()
    оператор if всегда будет возвращать значение true.
    На удивление сложно написать правильный код, когда задействованные типы могут быть простыми (не ссылочными) типами или ссылочными типами (хотя такие классы трансформации типов, как remove_reference (см. раздел 16.2.3), вполне могут помочь в этом).
    На практике параметры в виде ссылки на r-значение используются в одном из двух случаев:
    либо когда шаблон перенаправляет свои аргументы, ли когда шаблон перегружается.
    Перенаправление рассматривается в разделе 16.2.7, а перегрузка шаблона в разделе 16.3, а пока достаточно знать, что стоит обратить внимание на то, что шаблоны функций,
    использующие ссылки на r-значение, зачастую используют перегрузку таким же образом, как описано в разделе 13.6.3: template <typename Т> void f(Т&&); // привязка к не константным
    // r-значениям template <typename Т> void f(const T&); //
    Page 866/1103
    l-значения и константные
    // r-значения
    Подобно нешаблонным функциям, первая версия будет связана с изменяемым r-значением,
    а вторая с l-значением или константным r-значением. Упражнения раздела 16.2.5
    Упражнение 16.42. Определите типы Т и val в каждом из следующих вызовов: template <typename Т> void g(T&& val); int i = 0; const int ci = i;
    (a) g(i); (b) g(ci); (c) g(i * ci);
    Упражнение 16.43. Используя определенную в предыдущем упражнении функцию, укажите,

    1   ...   41   42   43   44   45   46   47   48   ...   54


    написать администратору сайта