Что делают компиляторы с разветвлением во время компиляции?

EDIT: В качестве примера я использовал случай if if else, который иногда может быть разрешен во время компиляции (например, когда задействованы статические значения, cf ). Адаптация ответов ниже для других типов статического ветвления (например, нескольких ветвей или ветвей с несколькими критериями) должна быть простой. Обратите внимание, что разветвление во время компиляции с использованием шаблона-мета-программирования не является темой здесь.


В типичном коде, подобном этому

 #include  template  T numeric_procedure( const T& x ) { if ( std::is_integral::value ) { // Integral types } else { // Floating point numeric types } } 

будет ли компилятор оптимизировать выражение if / else, когда я буду определять конкретные типы шаблонов позже в моем коде?

Простой альтернативой было бы написать что-то вроде этого:

 #include  template  inline T numeric_procedure( const T& x ) { return numeric_procedure_impl( x, std::is_integral() ); } // ------------------------------------------------------------------------ template  T numeric_procedure_impl( const T& x, std::true_type const ) { // Integral types } template  T numeric_procedure_impl( const T& x, std::false_type const ) { // Floating point numeric types } 

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

TL; DR

Существует несколько способов получить различное поведение во время выполнения, зависящее от параметра шаблона. Производительность не должна быть вашей главной задачей здесь, но гибкость и ремонтопригодность должны быть. Во всех случаях различные тонкие обертки и постоянные условные выражения будут оптимизированы на любом достойном компиляторе для создания релизов. Ниже небольшое резюме с различными компромиссами (вдохновлено этим ответом @AndyProwl).

Время выполнения, если

Первое решение – это простое время выполнения, if :

 template T numeric_procedure(const T& x) { if (std::is_integral::value) { // valid code for integral types } else { // valid code for non-integral types, // must ALSO compile for integral types } } 

Он прост и эффективен: любой достойный компилятор оптимизирует мертвую ветку.

Существует несколько недостатков:

  • на некоторых платформах (MSVC) постоянное условное выражение дает ложное предупреждение о компиляторе, которое затем необходимо игнорировать или отключать.
  • Но, что еще хуже, на всех соответствующих платформах обе ветви оператора if/else должны действительно компилироваться для всех типов T , даже если одна из ветвей, как известно, не должна быть взята. Если T содержит разные типы членов в зависимости от его характера, то вы получите ошибку компилятора, как только вы попытаетесь получить к ним доступ.

Отправка тегов

Ваш второй подход известен как диспетчеризация тегов:

 template T numeric_procedure_impl(const T& x, std::false_type) { // valid code for non-integral types, // CAN contain code that is invalid for integral types } template T numeric_procedure_impl(const T& x, std::true_type) { // valid code for integral types } template T numeric_procedure(const T& x) { return numeric_procedure_impl(x, std::is_integral()); } 

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

Основной (незначительный недостаток IMO) заключается в том, что у вас есть шаблон с 3 вместо 1 функции.

SFINAE

Тесно связанная с отправкой тегов – SFINAE (сбой замены не является ошибкой)

 template::value>::type> T numeric_procedure(const T& x) { // valid code for non-integral types, // CAN contain code that is invalid for integral types } template::value>::type> T numeric_procedure(const T& x) { // valid code for integral types } 

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

Недостаток заключается в том, что он может быть хрупким и сложным способом, если вы точно не знаете, что такое весь набор перегрузки (например, с тяжелым кодом шаблона, ADL может вывести больше перегрузок из связанных пространств имен, о которых вы не думали ). И по сравнению с диспетчеризацией меток выбор, основанный на чем-либо, кроме бинарного решения, намного более активен.

Частичная специализация

Другой подход заключается в использовании вспомогательного элемента шаблона classа с оператором приложения приложения и частично его специализации

 template struct numeric_functor; template struct numeric_functor { T operator()(T const& x) const { // valid code for non-integral types, // CAN contain code that is invalid for integral types } }; template struct numeric_functor { T operator()(T const& x) const { // valid code for integral types } }; template T numeric_procedure(T const& x) { return numeric_functor::value>()(x); } 

Это, вероятно, самый гибкий подход, если вы хотите иметь мелкомасштабный контроль и минимальное дублирование кода (например, если вы также хотите специализироваться на размере и / или выравнивании, но говорите только для типов с плавающей точкой). Соответствие шаблонов, заданное с помощью частичной специализированности шаблонов, идеально подходит для таких сложных задач. Как и для диспетчеризации меток, вспомогательные функции оптимизируются любым достойным компилятором.

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

Если constexpr (предложение C ++ 1z)

Это перезагрузка неудачных ранее предложений для static if (которые используются на языке программирования D)

 template T numeric_procedure(const T& x) { if constexpr (std::is_integral::value) { // valid code for integral types } else { // valid code for non-integral types, // CAN contain code that is invalid for integral types } } 

Как и во время выполнения, if все в одном месте, но главным преимуществом здесь является то, else ветвь else будет полностью отброшена компилятором, когда, как известно, ее не следует принимать. Большим преимуществом является то, что вы сохраняете весь код локальным и не должны использовать небольшие вспомогательные функции, такие как диспетчеризация меток или частичная специализация шаблонов.

Concepts-Lite (предложение C ++ 1z)

Concepts-Lite – это предстоящая техническая спецификация, которая должна стать частью следующей крупной версии C ++ (C ++ 1z, с z==7 как наилучшее предположение).

 template T numeric_procedure(const T& x) { // valid code for non-integral types, // CAN contain code that is invalid for integral types } template T numeric_procedure(const T& x) { // valid code for integral types } 

Этот подход заменяет ключевое слово class или typename внутри template< > скобками названием концепции, описывающим семейство типов, для которых должен работать код. Это можно рассматривать как обобщение методов диспетчеризации меток и SFINAE. Некоторые компиляторы (gcc, Clang) имеют экспериментальную поддержку этой функции. Прилагательное Lite ссылается на неудачное предложение Concepts C ++ 11.

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

То есть:

 int foo() { #if 0 return std::cout << "this isn't going to work\n"; #else return 1; #endif } 

будет работать нормально, потому что препроцессор удаляет мертвую ветвь, прежде чем компилятор увидит ее, но:

 int foo() { if (std::is_integral::value) { return std::cout << "this isn't going to work\n"; } else { return 1; } } 

не будет. Несмотря на то, что оптимизатор может отбросить первую ветвь, она все равно не скомпилируется. Здесь используется справка enable_if и SFINAE, так как вы можете выбрать допустимый (компилируемый) код и недействительный (несовместимый) код «Сбой компиляции не является ошибкой».

Компилятор может быть достаточно умным, чтобы увидеть, что он может заменить тело оператора if двумя различными реализациями функций и просто выбрать правильный. Но с 2014 года я сомневаюсь, что есть какой-то компилятор, который достаточно умен, чтобы сделать это. Возможно, я ошибаюсь. С std::is_integral , std::is_integral достаточно прост, и я думаю, что он будет оптимизирован.

Ваша идея перегрузки по результату std::is_integral – одно из возможных решений.

Другое и более эффективное решение IMHO – использовать std::enable_if (вместе с std::is_integral ).

Кредит @MooingDuck и @Casey

 template decltype(auto) if_else_impl(std::true_type, FN1 &&fn1, FN2 &&, Args&&... args) { return fn1(std::forward(args)...); } template decltype(auto) if_else_impl(std::false_type, FN1 &&, FN2 &&fn2, Args&&... args) { return fn2(std::forward(args)...); } #define static_if(...) if_else_impl(__VA_ARGS__, *this) 

И обычай прост как:

 static_if(do_it, [&](auto& self){ return 1; }, [&](auto& self){ return self.sum(2); } ); 

Работает как статический, если – компилятор переходит только в «истинную» ветвь.


PS Вам нужно иметь self = *this и делать из него вызовы членов, из-за ошибки gcc . Если у вас есть вложенные lambda-вызовы, вы не можете использовать this-> вместо self.

  • Когда следует использовать Observer и Observable
  • Почему мне приходится обращаться к членам базового classа шаблонов через этот указатель?
  • Нестандартные параметры шаблона
  • Двухэтапный поиск - требуется объяснение
  • Что означает шаблон ?
  • gcc может скомпилировать вариационный шаблон, в то время как clang не может
  • Существует ли типичный шаблон реализации государственного аппарата?
  • Каковы некоторые параметры шаблона шаблона?
  • Почему необходим allocator :: rebind, когда у нас есть параметры шаблона шаблона?
  • НЕ используя шаблон репозитория, используйте ORM as is (EF)
  • Вывод первого аргумента шаблона с другими параметрами шаблона по умолчанию
  • Interesting Posts

    Угловая ошибка DI – ИСКЛЮЧЕНИЕ: Не удается разрешить все параметры

    Передача аргументов в селектор в Swift

    Беспроводное подключение к Интернету подключается, но интернет не работает (пакеты не получены). Проводной

    OS X 10.6 Snow Leopard больше не монтирует внешний USB-накопитель

    Айви не может разрешить зависимость, не в состоянии найти причину

    Как редактировать known_hosts, когда несколько хостов используют одно и то же имя IP и DNS?

    Что такое ApplicationException для .NET?

    Как получить доступ к Spring-boot JMX удаленно

    Как сопоставить несколько результатов с помощью std :: regex

    Как прокручивать элемент в UWP

    Bluetooth не работает в Windows 10

    Как изменить конфигурацию прокси-сервера Windows7 из командной строки?

    Создание элементов из 3 коллекций с использованием Linq

    Может ли указатель на базовую точку указывать на массив производных объектов?

    Максимальный размер кэша приложений для iPad / iPhone

    Давайте будем гением компьютера.