Sfinae c что это

SFINAE — это просто

Sfinae c что это. 4c533ea40b63346a50df2484ef60833b. Sfinae c что это фото. Sfinae c что это-4c533ea40b63346a50df2484ef60833b. картинка Sfinae c что это. картинка 4c533ea40b63346a50df2484ef60833b

Здравствуйте, коллеги.
Хочу рассказать о SFINAE, интересном и очень полезном (к сожалению*) механизме языка C++, который, однако, может представляться неподготовленному человеку весьма мозгоразрывающим. В действительности принцип его использования достаточно прост и ясен, будучи сформулирован в виде нескольких чётких положений. Эта заметка рассчитана на читателей, обладающих базовыми знаниями о шаблонах в C++ и знакомых, хотя бы шапочно, с C++11.
* Почему к сожалению? Хотя использование SFINAE — интересный и красивый приём, переросший в широко используемую идиому языка, гораздо лучше было бы иметь средства, явно описывающие работу с типами.

Рассмотрим простой пример:

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

Источник

Как сделать SFINAE изящным и надежным

И снова здравствуйте. Делимся с вами интересной статьёй, перевод которой подготовлен специально для студентов курса «Разработчик C++».

Sfinae c что это. image loader. Sfinae c что это фото. Sfinae c что это-image loader. картинка Sfinae c что это. картинка image loader

Сегодня у нас гостевой пост Адама Балаша (Ádám Balázs). Адам является инженером-программистом в Verizon Smart Communities Hungary и занимается разработкой видеоаналитики для встраиваемых систем. Одна из его страстей — оптимизации времени компиляции, поэтому он сразу согласился написать гостевой пост на эту тему. Вы можете найти Адама в онлайне на LinkedIn.

Просто взгляните на его оригинальную форму:

И сравните ее с этой более выразительной формой:

Мы можем разумно полагать, что уже можно расслабиться и начать использовать его в производстве. Мы могли бы, он работает в большинстве случаев, но — как мы говорим об интерфейсах — наш код должен быть безопасным и надежным. Так ли это? Давайте попробуем взломать его!

Недостаток № 1: SFINAE можно обойти

Обычно SFINAE используется для отключения части кода в зависимости от условия. Это может быть очень полезно, если нам нужно реализовать, например, пользовательскую функцию abs по какой-либо причине (пользовательский арифметический класс, оптимизация для конкретного оборудования, в учебных целях и т. д.):

Эта программа выводит следующее, что выглядит вполне нормально:

Действительно, теперь программа выводит:

a: 4294967295 myAbs( a ): 1

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

Код работает должным образом: вызов myAbs с беззнаковым типом вызывает ошибку времени компиляции:

candidate template ignored: requirement ‘std::is_signed_v unsigned int>’ was not satisfied [with T = unsigned int]

Взлом SFINAE состояния

Тогда что не так с этой функцией? Чтобы ответить на этот вопрос, мы должны проверить, как myAbs реализует SFINAE.

Второй параметр шаблона является анонимным, имеет тип по умолчанию, но он все еще является параметром шаблона, поэтому его можно явно указать. Является ли это проблемой? В этом случае это действительно огромная проблема. Мы можем использовать третью форму, чтобы обойти нашу SFINAE-проверку:

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

Мы решим эту проблему — но сначала: есть ли другие недостатки? Что ж…

Недостаток № 2: У нас не может быть конкретных реализаций

Другое распространенное использование SFINAE — предоставление конкретных реализаций для определенных условий времени компиляции. Что, если мы не хотим полностью запретить вызов myAbs со значениями без знака и предоставляем тривиальную реализацию для этих случаев? Мы можем использовать if constexpr в C ++ 17 (мы рассмотрим это позже), или же мы можем:

Ой-ой, стандарт C++ (C++ 17; §17.1.16) гласит следующее:

«Аргументы по умолчанию не должны предоставляться параметру шаблона двумя разными объявлениями в одной и той же области видимости».

Упс, это именно то, что мы сделали…

Почему бы не использовать обычный if?

Мы могли бы просто использовать if во время выполнения вместо этого:

Наш код сразу даст сбой:

Это ограничение — то, что устраняет SFINAE: мы можем написать код, который действителен только для подмножества T (в myAbs действителен только для беззнаковых типов или действителен только для знаковых типов).

Решение: еще одна форма для SFINAE

Что мы можем сделать, чтобы преодолеть эти недостатки? Для первой проблемы мы должны принудительно проводить нашу SFINAE-проверку независимо от того, как пользователи вызывают нашу функцию. В настоящее время нашу проверку можно обойти, когда компилятору не нужен тип по умолчанию для второго параметра шаблона.

Что если мы используем наш SFINAE-код для объявления типа параметра шаблона вместо предоставления типа по умолчанию? Давайте попробуем:

Нам нужно, чтобы IsSigned был типом, отличным от void в допустимых случаях, потому что мы хотим предоставить значение по умолчанию для этого типа. Для типа void нет значения, поэтому мы должны использовать что-то другое: bool, int, enum, nullptr_t и т. д. Обычно я использую bool — в этом случае выражения выглядят осмысленно:

Оно работает! Для myAbs (5u) компилятор выдает ошибку, как и раньше:

Наконец, мы больше не можем обводить myAbs вокруг пальца: myAbs unsigned int, true> (5u) вызывает ошибку. Неважно, если мы предоставляем в вызове значение по умолчанию или нет, часть выражения SFINAE оценивается в любом случае, потому что компилятору нужен тип аргумента анонимного значения шаблона.

Мы можем перейти к следующей проблеме — но погодите минуту! Я думаю, что мы больше не переопределяем аргумент по умолчанию для того же параметра шаблона Какова была исходная ситуация?

Но теперь с текущим кодом:

Это означает, что мы не переопределяем аргументы по умолчанию, так как есть только одна функция с аргументом шаблона bool, другая — неудавшаяся подстановка, поэтому она не существует.

Синтаксический сахар

UPD. Данный абзац был удален автором из-за обнаружившихся в нем ошибок.

Старые версии C ++

Все вышеперечисленное работает с C++11, единственное отличие — многословность определений ограничений между стандартными версиями:

Но шаблон остается прежним:

В старом добром C++98 нет псевдонимов шаблонов, кроме того, шаблоны функций не могут иметь типы или значения по умолчанию. Мы можем вставить наш SFINAE-код в тип результата или только в список параметров функции. Рекомендуется второй вариант, потому что конструкторы не имеют типов результатов. Лучшее, что мы можем сделать, это что-то вроде этого:

Просто для сравнения — современная версия C++:

Версия C++98 уродлива, вводит бессмысленный параметр, но она работает — вы можете использовать ее, если это крайне необходимо. И да: my_enable_if и my_is_signed должны быть реализованы ( std :: enable_if и std :: is_signed были новыми в C++11).

Современное состояние

C++17 ввел if constexpr — способ для отбрасывания кода на основе условий во время компиляции. Оба оператора if и else должны быть синтаксически корректны, но условие будет оцениваться во время компиляции.

Как мы видим, наша функция abs стала более компактной и удобной для чтения. Однако обработка несоответствующих типов не является однозначной. Закомментированный безусловный static_assert делает это утверждение плохо согласованным, что запрещено стандартом, независимо от того, будет оно отброшено или нет.

К счастью, существует лазейка: в шаблонных объектах отброшенные операторы не создаются, если условие не зависит от значения. Отлично!

Таким образом, единственная проблема с нашим кодом состоит в том, что он дает сбой во время определения шаблона. Если бы мы могли отложить оценку static_assert до времени создания шаблона, проблема была бы решена: он был бы создан тогда и только тогда, когда все наши условия false. Но как мы можем отложить static_assert до создания шаблона? Сделайте его условие зависимым от типа!

О будущем

Мы действительно уже близки, но нужно еще немного подождать, пока C++20 принесет окончательное решение: концепции (concept)! Это полностью изменит способ использования шаблонов (и SFINAE).

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

И как мы можем использовать концепции? Есть три способа:

Обратите внимание, что третья форма все еще объявляет функцию шаблона! Вот полная реализация myAbs в C++20:

Закомментированный вызов дает следующую ошибку:

Я призываю всех смело использовать эти методы в производственном коде, время компиляции дешевле, чем время выполнения. Happy SFINAEing!

Источник

Применение SFINAE для специализации шаблонных методов в зависимости от типа контейнера std::map или std::vector, передаваемых в качестве аргумента

Встала задача: «написать вторую реализацию шаблонной функции», в которую передаются одни и те же аргументы. То есть сигнатура обеих функций полностью совпадает. Разница заключается в том, что в качестве аргумента в функцию могут передаваться как контейнеры std::vector, так и контейнеры std::map. И уже в зависимости от того std::vector это или std::map, должна выбираться та или иная реализация.

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

Наш случай будет вторым. Имеется две перегрузки шаблонной функции.

Реализация

Стандартная библиотека STD, как оказалось не имеет функционала для определения, является ли контейнер std::map или нет. Но на StackOverflow уже обсуждался этот вопрос. Приведённый там код нужно лишь немного поправить, и правильно применить, чтобы написать специализацию функций в зависимости от типа контейнера.

Суть кода заключается в том, чтобы на этапе компиляции решить, является ли элемент контейнера std::pair и на основании этого решить, является ли контейнер std::map. Если контейнер является std::map, то выбираем одну функцию, в остальных случаях выбираем вторую реализацию функции.

Используемые стандарты C++

Для работы данного кода необходимо использовать стандарт C++17, на меньшем стандарте код не скомпилируется.

В проектах на QMake в pro файл необходимо добавить следующие строки

Заключение

В качестве заключения получаем следующий вывод

Как это выглядит в консоли

Sfinae c что это. . Sfinae c что это фото. Sfinae c что это-. картинка Sfinae c что это. картинка

Sfinae c что это. timeweb 120 90. Sfinae c что это фото. Sfinae c что это-timeweb 120 90. картинка Sfinae c что это. картинка timeweb 120 90

Рекомендуем хостинг TIMEWEB

Рекомендуемые статьи по этой тематике

Источник

SFINAE

Compiler support
Freestanding and hosted
Language
Standard library headers
Named requirements
Feature test macros (C++20)
Language support library
Concepts library (C++20)
Diagnostics library
General utilities library
Strings library
Containers library
Iterators library
Ranges library (C++20)
Algorithms library
Numerics library
Localizations library
Input/output library
Filesystem library (C++17)
Regular expressions library (C++11)
Atomic operations library (C++11)
Thread support library (C++11)
Technical specifications
Symbols index
External libraries
Parameters and arguments
Class templates
Function templates
Class member templates
Variable templates (C++14)
Template argument deduction
Class template argument deduction (C++17)
Explicit (full) specialization
Partial specialization
Dependent names
Parameter packs (C++11)
sizeof. (C++11)
Fold-expressions (C++17)
SFINAE
Constraints and concepts (C++20)

«Substitution Failure Is Not An Error»

This rule applies during overload resolution of function templates: When substituting the explicitly specified or deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.

This feature is used in template metaprogramming.

Contents

[edit] Explanation

Function template parameters are substituted (replaced by template arguments) twice:

Substitution occurs in

A substitution failure is any situation when the type or expression above would be ill-formed (with a required diagnostic), if written using the substituted arguments.

Only the failures in the types and expressions in the immediate context of the function type or its template parameter types or its explicit specifier (since C++20) are SFINAE errors. If the evaluation of a substituted type/expression causes a side-effect such as instantiation of some template specialization, generation of an implicitly-defined member function, etc, errors in those side-effects are treated as hard errors. A lambda expression is not considered part of the immediate context. (since C++20)

This section is incomplete
Reason: mini-example where this matters

Substitution proceeds in lexical order and stops when a failure is encountered.

If there are multiple declarations with different lexical orders (e.g. a function template declared with trailing return type, to be substituted after a parameter, and redeclared with ordinary return type that would be substituted before the parameter), and that would cause template instantiations to occur in a different order or not at all, then the program is ill-formed; no diagnostic required.

[edit] Type SFINAE

The following type errors are SFINAE errors:

[edit] Expression SFINAE

The following expression errors are SFINAE errors

Only constant expressions that are used in types (such as array bounds) were required to be treated as SFINAE (and not hard errors) before C++11.

[edit] SFINAE in partial specializations

Deduction and substitution also occur while determining whether a specialization of a class or variable (since C++14) template is generated by some partial specialization or the primary template. Compilers do not treat a substitution failure as a hard-error during such determination, but ignore the corresponding partial specialization declaration instead, as if in the overload resolution involving function templates.

Notes: currently partial specialization SFINAE is not formally supported by the standard (see also CWG issue 2054), however, LFTS requires it works since version 2 (see also detection idiom).

[edit] Library support

The standard library component std::enable_if allows for creating a substitution failure in order to enable or disable particular overloads based on a condition evaluated at compile time.

In addition, many type traits must be implemented with SFINAE if appropriate compiler extensions are unavailable.

The standard library component std::void_t is another utility metafunction that simplifies partial specialization SFINAE applications.

[edit] Alternatives

static_assert (since C++11) is usually preferred over SFINAE if only a conditional compile time error is wanted.

[edit] Examples

A common idiom is to use expression SFINAE on the return type, where the expression uses the comma operator, whose left subexpression is the one that is being examined (cast to void to ensure the user-defined operator comma on the returned type is not selected), and the right subexpression has the type that the function is supposed to return.

Источник

Грань использования SFINAE

Часто вижу примеры SFINAE в поиске метода в классе или проверка на наследование, чтобы определить на этапе компиляции ошибку. Но эти же вещи «слетят» при компиляции и без SFINAE и будет ошибка компиляции.

Если не учитывать ситуацию, когда на этапе компиляции отбрасывается не нужный претендент класса и вся эта магия (потому, что я считаю эту часть SFINAE действительно полезной). Зачем используют SFINAE для всяких проверок: есть ли метод, есть ли enum, есть ли конструктор, который принимает строку? Только для связки с той магией?

2 ответа 2

SFINAE нужно для того, чтобы убрать из списка перегрузки тот или иной шаблон функции, чтобы вместо него использовалась другая перегрузка функции, например:

В данном примере, без SFINAE не было бы никакой ошибки компиляции:

Если перегрузка функции одна, и нужна просто проверка времени компиляции, то вместо SFINAE надо использовать static_assert :

Насколько мне кажется, смысл проверки — получить ошибку быстро и в нужном месте — раз, и перенести проверку из уровня синтаксиса в уровень семантики — два.

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

Затем, если какая-то штука компилируется, это ещё не значит, что она правильна. Например, один и тот же код может иметь смысл только для указателей, но в принципе компилироваться и для int ‘ов. Если использование шаблона с int ‘ом не имеет смысла, лучше не позволять юзеру писать бессмысленный код, а как можно скорее проинформировать его об этом.

(Надеюсь, я правильно понял смысл вашего вопроса.)

Источник

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

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