С++. Знакомство с decltype, по следам Скотт Мейерса

decltype используется для сообщения типа. Это один из механизмов вывода типа.

  • Для написания этой статьи использовались материалы книги "Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14", написанной Скоттом Мейерсом
  • Схематичный исходный код работы decltype в простых случаях

  • Обычно то, что сообщает decltype обозначает такой тип данных, который легко обозначить мимолётным взглядом на переменную или выражение

Так как это механизм вывода типа, то можно использовать подстановку этого обозначения для обозначения типа переменных:

Вот такой вот механизм вывода типа.

  • Обычно тип того, что мы отдаём вовнутрь decltype, decltype нам и сообщает. Но "обычно" обозначает "обычно", а не "всегда"
  • Основное применение decltype в С++11 — объявление шаблонов функций, в которых возвращаемый тип функцией зависит от её параметров

Возвращаемый оператором operator[] контейнера тип зависит от самого контейнера. Чтобы было понятнее, разберите пример, где в одинаковых условиях происходят разные результаты:

  • Пример с попыткой возвращения [] из функции

Эта нелогичность поведения как раз объясняется выводом типа с помощью auto , а такой вывод происходит из-за того, что в первом случае контейнер хранит bool, где при использовании auto для [] получается новый объект, а во втором контейнер хранит int, где при использовании auto для [] получается указатель на начало существующего объекта. Но эта тема не об этом. Эта тема о том, что использование decltype упрощает выражение этой зависимости. Кому-то может показаться, что вместо использования auto нужно использовать тип, выведенный шаблоном.

Да, auto — это не выводимый шаблоном тип T, выводятся типы по-разному. Это работает и даже может быть работает как кто-то и задумал, но есть одно большое НО.

    В параметрах нашей функции типы принимаемых параметров указаны как константные ссылки.

Константные! Ссылки! Согласно правилам языка, невозможно переинициализировать ссылку, она должна быть инициализирована сразу после создания. Может быть кто-то хотел конкретный тип, именно эту самую константную ссылку для внутренней локальной переменной. Тогда x=newValue; непозволительное выражение, компилятор не должен давать скомпилировать такую программу, где происходят попытки переинициализации ссылки. Здесь уже на сцену выходит decltype и, отталкивая auto в одну сторону, выводимый шаблоном тип T в другую, важно занимает позицию. Хотя и этот гордый decltype не всегда повлечёт прогнозируемое, но об этом позднее.

  • Заведомо некомпилируемый код, константы нельзя изменять, компилятор не даёт этого делать
    • Некоторые компиляторы компилировать это станут, но компилировать это компиляторы не обязаны

Вот такое имеется различие в выводах типов. Но это далеко не всё, что можно рассказать и имеет смысл знать. Вернёмся к тому, что в случае с [] тип может определяться как ссылочный, так и не ссылочный.

vector<bool> [] возвращает bool
vector<int> [] возвращает int&

Вот эта каверзность [] может кому-то сильно мешать. Кто-то может быть хочет написать функцию, из которой возвращать такое значение, которое будет обозначать ссылку на какой-то элемент вектора. Возвращать [].

Догадываюсь, что слова трудно написаны, но сейчас будет понятнее. Вот пример подобия написания такой функции:

  • Пример с попыткой возвращения [], который ниже имеется в виду

Это такой вариант присваивания значения в контейнер по индексу, где по каким-то причинам программист решил, что так удобнее. Но этот код не компилируется.
В этом примере auto не имеет ничего общего с выводом типа auto , оно только-лишь обозначает, что будет использован завершающий возвращаемый тип.
В С++11 этот тип указывался после описания параметров с помощью ->

  • auto в возвращаемом типе фунции или параметре лямбда-выражения влечёт применение вывода типа шаблона, а не вывода типа auto

В С++14 указание типа стало можно опускать. Использование такого указания типа способствовало выбору возвращаемого типа из типа любого параметра функции.

В примере с попыткой возвращения [ ], который чуть выше, функция auth_and_access возвращает тот тип, который возвращает [] при применении к данному контейнеру. Вовнутрь функции подаётся вектор, хранящий int и некоторый номер индекса. Функция возвращает элемент вектора, взятый по этому поданному туда индексу, указанному в [], сам этот [] возвращает int& (ссылку на int).

    Вывод возвращаемого типа использованием auto для auth_and_access, отбрасывает ссылку, функция auth_and_access отдаёт просто int.

Этот тип int, который отдаёт функция является rvalue (это значит, что во что-то можно присвоить то, что отдаёт функция, а в отдаваемое функцией присвоить ничего нельзя).

В С++14 возможен такой синтаксис: decltype( auto ).

  • auto говорит о том, что тип должен быть выведен
  • decltype о том, что в процессе вывода должны использоваться правила decltype

Возможно написание программы таким образом:

  • Рабочий код c decltype( auto )

В C++14 это работает.

  • auto указывает, что тип должен быть выведен
  • decltype говорит о том, что в процессе вывода должны использоваться правила decltype

Но этот код не лишён изъянов. Например, из любой функции с очень древних времён существования С++ можно возвращать структуру, в С++11 код мог бы выглядеть так:

В случае же попытки выполнения такого кода в нашем варианте с decltype( auto ), ничего путного не выйдет.

Здесь, в принципе, ошибка простая, все эти {} со значениями внутри, они для компиляторов — списки инициализации в первую очередь. Такое препятствие имеет место.

В остальном, эта функция может быть как в левой части выражения для копирования какого-то значения в возвращаемое ею (как в примере), так и в правой части выражения для копирования значения из возвращаемого ею, в какой-то отдельный объект. Сложно словами.

Интересные возможности. Но это снова не всё. Такой приём имеет ещё один недостаток. Нельзя в функцию auth_and_access передать временный объект. Параметр указан как неконстантня lvalue ссылка (&).

  • Листинг передачи временного объекта в функцию auth_and_access, (с заведомо неправильным параметром, указанном для функции)

Здесь, в параметрах, отдаваемых вовнутрь функции, отдаётся временный вектор, который прекратит своё существование с прекращением существования породившего его вызова функции. Такие временные объекты в большинстве случаев создаются компилятором, а не программистом, но и программистом тоже. Любая символьная константа может представлять собой временный объект:

Но тема не о временных объектах, такие временные объекты называются rvalue, или правоассоциативное (то, что с правой части и должно влиять на левую). Смысл в том, что этот объект имеет то же время жизни, которое имеет функция с момента вызова до возврата из неё значения. Из-за этого легко словить ошибку неопределённого поведения. Если создать ссылку на элемент, возвращаемый из такой функции, где в функцию передаётся временный объект, то вы получите по рукам и можете услышать клятое UB.

До С++11 было невозможно получить доступ для модификаций таких временных объектов, а после принятия стандарта С++11 стало возможно. Чтобы функция могла работать с таким временным объектом, и внутри функции можно было его модифицировать, нужно указать, что параметр, приходящий вовнутрь функции является временным объектом, делается это с помощью &&.

А вот если ссылаться на элемент ссылкой, то это уже — UB (неопределённое поведение)

Будьте внимательны и помните об этом.

Для С++14 окончательный код имеет вот такой вид:

Есть в decltype ещё кое что, что следует понимать, осознавать.

  • Примененине decltype к имени даёт объявленный тип для этого имени. Для lvalue выражений более сложных, чем имена, decltype гарантирует, что возвращаемы тип будет lvalue ссылкой

В коде это выглядит так:

decltype почти всегда возвращает, что мы от него хотим, но почти — это не всегда. Если имя представляет собой больше выражение, чем имя, то ждём от decltype ссылку.

Чтобы было понятнее, более полный пример:

Этот пример показывает, что стоит быть особо внимательным к деталям. Ведь, если возвращать из функции имя, обёрнутое скобками (), то вернётся ссылка, а это звоночек к неопределённому поведению.



  • decltype почти всегда даёт тип переменной или выражения без каких-либо изменений
  • для lvalue выражений типа Т, отличных от имени, decltype гарантировано даёт Т&
  • C++14 поддерживает синтаксическую конструкцию decltype( auto ), которая подобно auto , выводит тип из его инициализатора, но выполняет вывод типа с использованием правил decltype

2 комментария на «“С++. Знакомство с decltype, по следам Скотт Мейерса”»

  1. Скотт Мейерс по этому поводу пишет так: “I have no idea why type deduction for auto and for templates is not identical. If you know, please tell me!”.

  2. Спасибо вам огромное. Наконец-то нашёл, что давно искал.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/oleg-zelenyak/praktikum-programmirovaniya-na-turbo-pascal-zadachi-algoritmy-i-resheniya/?lfrom=15589587

Последние комментарии

Яндекс.Метрика