Для чего нужен std c

Статья для тех, кто как и я не понимает, зачем нужен std::common_type

В данной статье мы рассмотрим, зачем в стандартной библиотеке нужна конструкция для вывода общего типа, как она реализована и как она работает.

Для чего нужен std c. Смотреть фото Для чего нужен std c. Смотреть картинку Для чего нужен std c. Картинка про Для чего нужен std c. Фото Для чего нужен std c

В начале я хотел бы сказать спасибо своему коллеге khandeliants, который помог прояснить некоторые неочевидные для меня моменты относительно трактовки стандарта С++, а также доработать примеры кода.

Зачем это пригодилось нам

Всё началось с того, что команда PVS-Studio начала активно дорабатывать ядро С++ анализатора. Одна из больших задач – внедрение новой системы типов. Сейчас она состоит из специально закодированных строчек, мы же хотим заменить её на иерархическую систему. Подробно на новой системе типов я останавливаться не буду. В общих чертах мы пытаемся сделать из этого:

Для чего нужен std c. Смотреть фото Для чего нужен std c. Смотреть картинку Для чего нужен std c. Картинка про Для чего нужен std c. Фото Для чего нужен std c

Для чего нужен std c. Смотреть фото Для чего нужен std c. Смотреть картинку Для чего нужен std c. Картинка про Для чего нужен std c. Фото Для чего нужен std c

Подробно и со смешными картинками о нашей новой и старой системе типов не так давно рассказывал мой коллега Юрий на конференции itCppCon21. Сейчас, как мне кажется, у него набралось материала на два или три новых доклада. Будем с нетерпением их ждать 🙂

У новой системы типов есть аналоги type_traits, они так же, как и их прародители, помогают модифицировать тип или получить о нём нужную информацию.

Недавно я писал реализацию std::common_type для нашей системы типов. Этот трейт часто применяется в метапрограммировании, когда требуется вывести общий тип для произвольного числа переданных типов. Нам же подобный трейт оказался удобен для вывода результирующего типа, например, когда мы обходим бинарное выражение и встречаем арифметическую операцию:

Код, который нужно было задействовать для такой операции, раньше был более громоздким и сложным, теперь же он выглядит довольно изящно.

Зачем он вообще нужен C++ разработчикам

Допустим, что мы хотим написать наивную реализацию шаблона функции для скалярного произведения двух векторов, причем векторы могут быть инстанцированы различными типами. Требуется, чтобы тип скалярного произведения выводился автоматически. Такой шаблон функции начиная с C++14 можно реализовать как-то так:

Подразумевается, что в функцию приходят векторы одинакового размера. В противном случае мы не сможем посчитать скалярное произведение и получим выход за границу массива.

Итак, функция делает именно то, что мы и хотели. Компилятор сам выводит результирующий тип из return statement за нас. Осталась одна проблема – для переменной result нужно как-то вывести общий тип.

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

Conditional operator

Как мне кажется, приводить описание работы тернарного оператора из стандарта будет лишним, так как оно довольно большое. Поэтому вкратце рассмотрим, как происходит вывод типа в основных случаях.

Для наглядности будем визуализировать результаты с помощью двух вещей:

Перейдём к случаям:

Случай 1

Если и второй, и третий операнд имеют тип void, то результат также имеет тип void. Такое возможно, например, если оба выражения содержат throw, либо вызовы функций, возвращающих void, либо явное преобразование к типу void. Пример кода с выводами сообщений компиляторов:

Если второй или третий операнд – выражение throw, то результирующий тип выводится из другого. Другой операнд при этом не должен быть типа void. Пример кода с выводами сообщений компиляторов:

Случай 2

Если второй и третий операнд имеют разные типы, и при этом один из них классового типа, то выбирается такая перегрузка, которая сформирует операнды одинаковых типов. Например, может быть выбран конвертирующий конструктор или оператор неявного преобразования. Пример кода c выводами сообщений компиляторов:

Если посмотреть Clang AST этого кода, то можно заметить:

Здесь Clang неявно вызвал конвертирующий конструктор для третьего операнда, отчего оба операнда стали типа IntWrapper.

Случай 3

Над вторым и третьим операндом применяются standard conversions: lvalue-to-rvalue, array-to-pointer и function-to-pointer. После этих конверсий возможны несколько ситуаций:

Второй и третий операнды имеют одинаковый тип, тогда таким же будет и результирующий. Пример кода c выводами сообщений компиляторов:

Также второй и третий операнды могут иметь арифметический тип или тип перечисления. Для арифметических типов и перечислений будут применяться usual arithmetic conversions для формирования общего типа, который и будет результирующим. Пример кода c выводами сообщений компиляторов:

Также один или оба операнда могут иметь тип указателя или указателя на член класса. Тогда применяются правила pointer conversions/pointer-to-member conversions, function pointer conversions и qualification conversions для формирования композитного типа указателя, который и будет результирующим. Пример кода c выводами сообщений компиляторов:

Также оба операнда могут иметь тип std::nullptr_t, либо один из них std::nullptr_t, а другой – константа нулевого указателя. Тогда результирующий тип – std::nullptr_t. Пример кода c выводами сообщений компиляторов:

Теперь мы видим, что вывести общий тип очень просто: в большинстве случаев достаточно воспользоваться тернарным оператором. Давайте отвлечемся от теории и попробуем применить только что приобретённые знания и написать обобщённый код, который будет это делать.

P.S.: Для того, чтобы написать нужный нам аналог std::common_type для новой системы типов (TypeTraits::CommonType), нам пришлось реализовать все вышеописанные и некоторые нерассмотренные правила вывода общего типа.

Пишем свой common_type

Вернёмся к нашей функции скалярного произведения векторов. Начиная с C++11 в нашем распоряжении есть спецификатор decltype, который возвращает тип переданного в него выражения. Мы уже использовали его выше для работы с type_printer. Из прошлого абзаца мы знаем, что если протолкнуть в него вызов тернарного оператора с объектами двух наших типов, то компилятор сделает за нас вывод общего типа.

Попробуем применить сказанное в действии:

Давайте подробно разберем, что делает этот код:

std::declval – это шаблон функции без реализации, который возвращает rvalue-ссылку на тип T. При типе T = void возвращает тип void. Этот шаблон чаще всего применяется внутри невычисляемого контекста (decltype, sizeof, requires. ) и позволяет как бы работать с объектом переданного типа, обходя вызов конструктора. Это особенно полезно, если тип T не имеет публичного конструктора по умолчанию либо он удален.

Не забываем, что нам также могут передать ссылки в качестве типа, поэтому стоит применить std::decay. Он уберет CV-квалификаторы, ссылки, добавит указатели функциям (function-to-pointer conversion) и преобразует массивы в указатели (array-to-pointer conversion):

Согласитесь – писать такое в коде совсем не хочется. Попробуем немного причесать код, для этого нам надо написать пару вспомогательных шаблонов классов для удобства. Во-первых, попробуем написать класс для вывода общего типа из двух переданных:

Теперь можем применить этот common_type в нашем коде:

Отлично, мы избавились от всей этой страшной портянки, код стал лаконичным. Но хочется теперь сделать common_type таким, чтобы он умел работать с любым числом переданных типов – от нуля до произвольного. Тогда немного поменяем наш основной шаблон класса и его специализации:

Стоит отметить, что примерно так common_type и реализован в стандартной библиотеке. Теперь, давайте подробно разберём, что тут происходит:

Как я уже говорил выше, более подробно, с правилами вывода типа для самого тернарного оператора можно ознакомиться в стандарте. Я использовал последний актуальный рабочий черновик, там их можно найти в главе 7.6.16. Сами черновики можно посмотреть, например, здесь. Также можно воспользоваться документацией на cppreference.

Заключение

Мы посмотрели, как работает std::common_type и, чтоб более подробно в нём разобраться, написали его реализацию, почитав стандарт, и даже немного затронули логику работы тернарного оператора. Надеюсь, эта статья оказалась полезна. Всем спасибо за внимание!

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Vladislav Stolyarov, Phillip Khandeliants. An article for those who, like me, do not understand the purpose of std::common_type.

Источник

Зачем часто писать std. если можно один раз using namespace std?

Зачем указывать using namespace std
Здравствуйте. Вроде разобрался с циклами, ветвлением, базовыми типами данных. Остается не.

#define PI 3.1415
float pi=PI;

#define PI 3.1415
float pi=PI;

конечно спс)) но я ничо непонял)) обязательно погуглю.

Добавлено через 5 минут

# видите? Значит уже разница. #define подает комманду препроцессору, что PI есть 3,1415

Директива define позволяет связать идентификатор (мы будем называть этот идентификатор замещаемой частью) с лексемой (возможно, что пустой!) или последовательностью лексем (строка символов является лексемой, заключённой в двойные кавычки), которую называют строкой замещения или замещающей частью директивы define.

#define PI 3.14159
Идентификаторы, которые используют для представления констант, называют объявленными или символическими константами. Например, последовательность символов, располагаемая после объявленной константы PI, объявляет константу 3.14159. Препроцессор заменит в оставшейся части программы все отдельно стоящие вхождения идентификатора PI на лексему, которую транслятор будет воспринимать как плавающий литерал 3.14159.

Препроцессор выполняет грубую предварительную работу по замене замещаемых идентификаторов замещающими строками. В этот момент ещё ничего не известно об именах, поскольку транслятор фактически ещё не начинал своей работы. А потому следует следить за тем, чтобы замещаемые идентификаторы входили в состав объявлений лишь как элементы инициализаторов.

Рассмотрим несколько примеров. Директива препроцессора

#define PI 3.14159
Превращает корректное объявление

float PI;
в синтаксически некорректную конструкцию

float 3.14159;
А следующее определение правильное.

Источник

/std (определение стандартной версии языка)

Включить поддерживаемые возможности языка C и C++ из заданной версии стандарта языка C или C++.

Синтаксис

/std:c++14
/std:c++17
/std:c++20
/std:c++latest
/std:c11
/std:c17

Remarks

/std параметры доступны в Visual Studio 2017 и более поздних версиях. Они используются для управления стандартными функциями языка программирования ISO C или C++, включенными во время компиляции кода. С помощью этих параметров можно отключить поддержку некоторых новых возможностей языка и библиотеки. это может нарушить существующий код, который соответствует определенной версии стандарта языка.

Поддержка стандартов C++

/std Параметр, действующий во время компиляции C++, может быть обнаружен с помощью _MSVC_LANG макроса препроцессора. Дополнительные сведения см. в статье Макросы препроцессора.

/std:c++14
/std:c++14 параметр позволяет включить стандартные функции c++ 14, реализуемые компилятором MSVC. Этот параметр используется по умолчанию для кода, скомпилированного как C++. он доступен начиная с Visual Studio 2015 с обновлением 3.

следующие функции остаются включенными, если /std:c++14 указан параметр, чтобы избежать критических изменений для пользователей, которые уже применяют зависимости от функций, доступных в или до Visual Studio 2015 с обновлением 2.

/std:c++17
/std:c++17 Параметр включает стандартные функции и поведение c++ 17. он включает полный набор функций c++ 17, реализованный компилятором MSVC. Этот параметр отключает поддержку компилятора и стандартной библиотеки для новых или измененных функций после C++ 17. Он специально отключает изменения после + + 17 в стандарте C++ и версиях рабочего черновика. Он не отключает обновление ретроактивного дефектов стандарта C++. этот параметр доступен начиная с Visual Studio 2017 версии 15,3.

в зависимости от версии или уровня обновления компилятора MSVC компоненты c++ 17 могут быть полностью реализованы или полностью согласованы при указании /std:c++17 параметра. Общие сведения о согласовании языка C++ в Visual C++ по версии выпуска см. в разделе соответствие языку в Microsoft C/C++.

/std:c++20 Параметр отключает поддержку компилятора и стандартной библиотеки для новых или измененных функций после c++ 20. Он специально отключает изменения, внесенные после + + 20, в стандарте C++ и версии рабочего черновика. В нем не отключаются обновления ретроактивного дефектов стандарта C++.

/std:c++latest
/std:c++latest Параметр включает все реализованные в настоящее время компилятор и стандартные функции библиотеки, предлагаемые для следующего черновика, а также некоторые выполняющиеся и экспериментальные функции. этот параметр доступен начиная с Visual Studio 2015 с обновлением 3.

в зависимости от версии компилятора MSVC или уровня обновления, c++ 17, c++ 20 или предложенных функций c++ 23 могут быть не полностью реализованы или полностью согласованы при указании /std:c++latest параметра. мы рекомендуем использовать последнюю версию Visual Studio для максимального соответствия стандартам. Общие сведения о согласовании языка C++ и библиотеки в Visual C++ по версии выпуска см. в статье соответствие стандартам языка C/c++.

в версиях Visual Studio 2019 до версии 16,11 /std:c++latest требуется включить все функции компилятора и стандартной библиотеки c++ 20.

Список поддерживаемых возможностей языка и библиотеки см. в разделе новые возможности C++ в Visual Studio.

/std:c++latest Параметр не включает функции, защищенные /experimental коммутатором, но может потребоваться для их включения.

Функции компилятора и библиотеки, включенные в, /std:c++latest могут появиться в будущем стандарте C++. Неутвержденные возможности предоставляются на условиях «как есть», могут удаляться без уведомления либо в них могут вноситься критические изменения.

Поддержка стандартов C

/std:c11
/std:c11 Параметр позволяет включить соответствие ISO C11. он доступен начиная с Visual Studio 2019 версии 16,8.

/std:c17
/std:c17 Параметр позволяет включить соответствие ISO C17. он доступен начиная с Visual Studio 2019 версии 16,8.

Поскольку для поддержки этих стандартов требуется новый препроцессор, /std:c11 /std:c17 параметры компилятора и устанавливают /Zc:preprocessor параметр автоматически. Если вы хотите использовать традиционный (устаревший) препроцессор для C11 или C17, необходимо /Zc:preprocessor- явно задать параметр компилятора. Установка /Zc:preprocessor- параметра может привести к непредвиденному поведению и не рекомендуется.

на момент выпуска и до Visual Studio 2019 версии 16,10 библиотеки Windows SDK и UCRT, установленные Visual Studio, еще не поддерживают код C11 и C17. требуется обновленная версия Windows SDK и UCRT. Дополнительные сведения и инструкции по установке см. в разделе Установка поддержки C11 и C17 в Visual Studio.

при указании /std:c11 или /std:c17 MSVC поддерживает все функции C11 и C17, необходимые для стандартов. /std:c11 /std:c17 Параметры компилятора и обеспечивают поддержку этих функциональных возможностей:

так как C17 в основном является исправлением ошибок в версии ISO C11, MSVC поддержка C11 уже включает все соответствующие отчеты об ошибках. Различия между версиями C11 и C17, за исключением __STDC_VERSION__ макроса, отсутствуют. Он разворачивается в 201112L для C11 и 201710L для C17.

Компилятор не поддерживает большинство дополнительных функций ISO C11. некоторые из этих необязательных функций C11 были необходимыми функциями C99, которые MSVC не реализованы в целях архитектуры. Вы можете использовать макросы тестирования компонентов, например, __STDC_NO_VLA__ чтобы определить уровни поддержки компилятора для отдельных компонентов. Дополнительные сведения о предопределенных макросах для C см. в разделе предопределенные макросы.

Поддержка многопоточности, атомарных или комплексных чисел не поддерживается.

Дополнительные сведения см. в разделе функции стандартной библиотеки C в области соответствия языка Microsoft C/C++.

Установка данного параметра компилятора в среде разработки Visual Studio

Откройте диалоговое окно Страницы свойств проекта. Подробнее см. в статье Настройка компилятора C++ и свойств сборки в Visual Studio.

Источник

Почему с ‘using namespace std;’ в *.cpp-файлах может быть очень плохо

То, что написано ниже, для многих квалифицированных C++ разработчиков будет прекрасно известным и очевидным, но тем не менее, я периодически встречаю using namespace std; в коде различных проектов, а недавно в нашумевшей статье про впечатления от высшего образования было упомянуто, что студентов так учат писать код в вузах, что и сподвигло меня написать эту заметку.

Для чего вообще придумали пространства имен в C++? Когда какие-то две сущности (типы, функции, и т.д.) имеют идентификаторы, которые могут конфликтовать друг с другом при совместном использовании, C++ позволяет объявлять пространства с помощью ключевого слова namespace. Всё, что объявлено внутри пространства имен, принадлежит только этому пространству имен (а не глобальному). Используя using мы вытаскиваем сущности какого-либо пространства имен в глобальный контекст.

А теперь посмотрим, к чему это может привести.

Допустим, вы используете две библиотеки под названием Foo и Bar и написали в начале файла что-то типа

. таким образом вытащив всё, что есть в foo:: и в bar:: в глобальное пространство имен.

Все работает нормально, и вы можете без проблем вызвать Blah() из Foo и Quux() из Bar. Но однажды вы обновляете библиотеку Foo до новой версии Foo 2.0, которая теперь еще имеет в себе функцию Quux().

Теперь у вас конфликт: и Foo 2.0, и Bar импортируют Quux() в ваше глобальное пространство имен. В лучшем случае это вызовет ошибку на этапе компиляции, и исправление этого потребует усилий и времени.

А вот если бы вы явно указывали в коде метод с его пространством имен, а именно, foo::Blah() и bar::Quux(), то добавление foo::Quux() не было бы проблемой.

Но всё может быть даже хуже!

В библиотеку Foo 2.0 могла быть добавлена функция foo::Quux(), про которую компилятор по ряду причин посчитает, что она однозначно лучше подходит для некоторых ваших вызовов Quux(), чем bar::Quux(), вызывавшаяся в вашем коде на протяжении многих лет. Тогда ваш код все равно скомпилируется, но будет молча вызывать неправильную функцию и делать бог весть что. И это может привести к куче неожиданных и сложноотлаживающихся ошибок.

Имейте в виду, что пространство имен std:: имеет множество идентификаторов, многие из которых являются очень распространенными (list, sort, string, iterator, swap), которые, скорее всего, могут появиться и в другом коде, либо наоборот, в следущей версии стандарта C++ в std добавят что-то, что совпадет с каким-то из идентификаторов в вашем существующем коде.

Если вы считаете это маловероятным, то посмотрим на реальные примеры со stackoverflow:

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

Второй пример на ту же тему: вместо функции swap() используется std::swap(). Опять же, никакой ошибки компиляции, а просто неправильный результат работы.

Так что подобное происходит гораздо чаще, чем кажется.

Источник

Статья для тех, кто как и я не понимает, зачем нужен std::common_type

В данной статье мы рассмотрим, зачем в стандартной библиотеке нужна конструкция для вывода общего типа, как она реализована и как она работает.

Для чего нужен std c. Смотреть фото Для чего нужен std c. Смотреть картинку Для чего нужен std c. Картинка про Для чего нужен std c. Фото Для чего нужен std c

В начале я хотел бы сказать спасибо своему коллеге Филиппу, который помог прояснить некоторые неочевидные для меня моменты относительно трактовки стандарта С++, а также доработать примеры кода.

Зачем это пригодилось нам

Всё началось с того, что команда PVS-Studio начала активно дорабатывать ядро С++ анализатора. Одна из больших задач – внедрение новой системы типов. Сейчас она состоит из специально закодированных строчек, мы же хотим заменить её на иерархическую систему. Подробно на новой системе типов я останавливаться не буду. В общих чертах мы пытаемся сделать из этого:

Для чего нужен std c. Смотреть фото Для чего нужен std c. Смотреть картинку Для чего нужен std c. Картинка про Для чего нужен std c. Фото Для чего нужен std c

Для чего нужен std c. Смотреть фото Для чего нужен std c. Смотреть картинку Для чего нужен std c. Картинка про Для чего нужен std c. Фото Для чего нужен std c

Подробно и со смешными картинками о нашей новой и старой системе типов не так давно рассказывал мой коллега Юрий на конференции itCppCon21. Сейчас, как мне кажется, у него набралось материала на два или три новых доклада. Будем с нетерпением их ждать 🙂

У новой системы типов есть аналоги type_traits, они также, как и их прародители, помогают модифицировать тип или получить о нём нужную информацию.

Недавно я писал реализацию std::common_type для нашей системы типов. Этот трейт часто применяется в метапрограммировании, когда требуется вывести общий тип для произвольного числа переданных типов. Нам же подобный трейт оказался удобен для вывода результирующего типа, например, когда мы обходим бинарное выражение и встречаем арифметическую операцию:

Код, который нужно было задействовать для такой операции, раньше был более громоздким и сложным, теперь же он выглядит довольно изящно.

Зачем он вообще нужен C++ разработчикам

Допустим, что мы хотим написать наивную реализацию шаблона функции для скалярного произведения двух векторов, причем векторы могут быть инстанцированы различными типами. Требуется, чтобы тип скалярного произведения выводился автоматически. Такой шаблон функции начиная с C++14 можно реализовать как-то так:

Подразумевается, что в функцию приходят векторы одинакового размера. В противном случае мы не сможем посчитать скалярное произведение и получим выход за границу массива.

Итак, функция делает именно то, что мы и хотели. Компилятор сам выводит результирующий тип из return statement за нас. Осталась одна проблема – для переменной result нужно как-то вывести общий тип.

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

Conditional operator

Как мне кажется, приводить описание работы тернарного оператора из стандарта будет лишним, так как оно довольно большое. Поэтому вкратце рассмотрим, как происходит вывод типа в основных случаях.

Для наглядности будем визуализировать результаты с помощью двух вещей:

Перейдём к случаям:

Случай 1

Если и второй, и третий операнд имеют тип void, то результат также имеет тип void. Такое возможно, например, если оба выражения содержат throw, либо вызовы функций, возвращающих void, либо явное преобразование к типу void. Пример кода с выводами сообщений компиляторов:

Если второй или третий операнд – выражение throw, то результирующий тип выводится из другого. Другой операнд при этом не должен быть типа void. Пример кода с выводами сообщений компиляторов:

Случай 2

Если второй и третий операнд имеют разные типы, и при этом один из них классового типа, то выбирается такая перегрузка, которая сформирует операнды одинаковых типов. Например, может быть выбран конвертирующий конструктор или оператор неявного преобразования. Пример кода c выводами сообщений компиляторов:

Если посмотреть Clang AST этого кода, то можно заметить:

Здесь Clang неявно вызвал конвертирующий конструктор для третьего операнда, отчего оба операнда стали типа IntWrapper.

Случай 3

Над вторым и третьим операндом применяются standard conversions: lvalue-to-rvalue, array-to-pointer и function-to-pointer. После этих конверсий возможны несколько ситуаций:

Второй и третий операнды имеют одинаковый тип, тогда таким же будет и результирующий. Пример кода c выводами сообщений компиляторов:

Также второй и третий операнды могут иметь арифметический тип или тип перечисления. Для арифметических типов и перечислений будут применяться usual arithmetic conversions для формирования общего типа, который и будет результирующим. Пример кода c выводами сообщений компиляторов:

Также один или оба операнда могут иметь тип указателя или указателя на член класса. Тогда применяются правила pointer conversions/pointer-to-member conversions, function pointer conversions и qualification conversions для формирования композитного типа указателя, который и будет результирующим. Пример кода c выводами сообщений компиляторов:

Также оба операнда могут иметь тип std::nullptr_t, либо один из них std::nullptr_t, а другой – константа нулевого указателя. Тогда результирующий тип – std::nullptr_t. Пример кода c выводами сообщений компиляторов:

Теперь мы видим, что вывести общий тип очень просто: в большинстве случаев достаточно воспользоваться тернарным оператором. Давайте отвлечемся от теории и попробуем применить только что приобретённые знания и написать обобщённый код, который будет это делать.

P.S.: Для того, чтобы написать нужный нам аналог std::common_type для новой системы типов (TypeTraits::CommonType), нам пришлось реализовать все вышеописанные и некоторые нерассмотренные правила вывода общего типа.

Пишем свой common_type

Вернёмся к нашей функции скалярного произведения векторов. Начиная с C++11 в нашем распоряжении есть спецификатор decltype, который возвращает тип переданного в него выражения. Мы уже использовали его выше для работы с type_printer. Из прошлого абзаца мы знаем, что если протолкнуть в него вызов тернарного оператора с объектами двух наших типов, то компилятор сделает за нас вывод общего типа.

Попробуем применить сказанное в действии:

Давайте подробно разберем, что делает этот код:

Не забываем, что нам также могут передать ссылки в качестве типа, поэтому стоит применить std::decay. Он уберет CV-квалификаторы, ссылки, добавит указатели функциям (function-to-pointer conversion) и преобразует массивы в указатели (array-to-pointer conversion):

Согласитесь – писать такое в коде совсем не хочется. Попробуем немного причесать код, для этого нам надо написать пару вспомогательных шаблонов классов для удобства. Во-первых, попробуем написать класс для вывода общего типа из двух переданных:

Теперь можем применить этот common_type в нашем коде:

Отлично, мы избавились от всей этой страшной портянки, код стал лаконичным. Но хочется теперь сделать common_type таким, чтобы он умел работать с любым числом переданных типов – от нуля до произвольного. Тогда немного поменяем наш основной шаблон класса и его специализации:

Стоит отметить, что примерно так common_type и реализован в стандартной библиотеке. Теперь, давайте подробно разберём, что тут происходит:

Как я уже говорил выше, более подробно, с правилами вывода типа для самого тернарного оператора можно ознакомиться в стандарте. Я использовал последний актуальный рабочий черновик, там их можно найти в главе 7.6.16. Сами черновики можно посмотреть, например, здесь. Также можно воспользоваться документацией на cppreference.

Заключение

Мы посмотрели, как работает std::common_type и, чтоб более подробно в нём разобраться, написали его реализацию, почитав стандарт, и даже немного затронули логику работы тернарного оператора. Надеюсь, эта статья оказалась полезна. Всем спасибо за внимание!

Источник

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

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