если constexpr vs sfinae - PullRequest
       9

если constexpr vs sfinae

0 голосов
/ 06 января 2019

С введением if constexpr в c++17 некоторые проблемы, которые были решены с помощью SFINAE времени компиляции в c++14 / c++11, теперь могут быть решены с помощью if constexpr с более простым синтаксисом.

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

#include <iostream>
#include <type_traits>

template <typename T>
void print_sfinae(T&& x)
{
  std::cout << x << std::endl;
}

template <typename T0, typename... T>
std::enable_if_t<(sizeof...(T) > 0)> print_sfinae(T0&& x, T&&... rest)
{
  std::cout << x << std::endl;
  print_sfinae(std::forward<T>(rest)...);
}

template <typename T0, typename... T>
void print_ifconstexpr(T0&&x, T&&... rest)
{
  if constexpr (sizeof...(T) > 0)
         {
            std::cout << x << std::endl;
            print_ifconstexpr(std::forward<T>(rest)...);
         }
  else
      std::cout << x << std::endl;
}

int main()
{
  print_sfinae(5, 2.2, "hello");
  print_ifconstexpr(5, 2.2, "hello");

  return 0;
}

Подпрограмма print_sfinae использует методы SFINAE из c++11, тогда как print_ifconstexpr выполняет ту же работу, используя if constexpr.

Можно ли предположить, что компилятор при оценке if constexpr полностью отбрасывает непроверенное условие и генерирует код только для ветви, которая удовлетворяет условию if constexpr? Стандарт определяет такое поведение для компилятора?

В более общем смысле, с точки зрения эффективности и генерируемого кода, решения, основанные на if constexpr, идентичны эквивалентным решениям, основанным на pre-c ++ 17 SFINAE?

Ответы [ 2 ]

0 голосов
/ 07 января 2019

C ++ определяет наблюдаемое поведение программы.

Обе части кода имеют как наблюдаемое поведение печати. ​​

Копирование ссылок, вызов функций, которые принимают ссылки и возвращают void, являются ненаблюдаемым поведением.

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

0 голосов
/ 06 января 2019

Можно ли предположить, что компилятор при оценке if constexpr полностью отбрасывает неподтвержденное условие и генерирует код только для ветви, которая удовлетворяет условию if constexpr? Стандарт определяет такое поведение для компилятора?

Стандарт определяет, что с [stmt.if] :

Если оператор if имеет форму if constexpr, значение условия должно быть контекстно-преобразованным константным выражением типа bool; эта форма называется constexpr, если оператор. Если значение преобразованного условия равно false, первое подзаголовок является оператором отбрасывания, в противном случае второе подзаголовок, если оно присутствует, является оператором отбрасывания. Во время создания экземпляра вмещающего шаблонного объекта, если условие не зависит от значения после его создания, отброшенное подсостояние (если оно есть) не создается.

Суть в том, что оператор отбрасывания не создан - это основная цель if constexpr как языковой функции, позволяющая вам написать:

template <typename T0, typename... T>
void print_ifconstexpr(T0&& x, T&&... rest)
{
    std::cout << x << std::endl;
    if constexpr (sizeof...(T) > 0) {
        print_ifconstexpr(std::forward<T>(rest)...);
    }
}

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

if constexpr не будет создавать рекурсивный вызов, если в rest... нет чего-то, так что это работает.

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

Форма if constexpr легче написать, легче понять и, несомненно, быстрее компилируется. Определенно предпочитаю это.

<ч />

Обратите внимание, что вашему первому примеру вообще не нужен SFINAE. Это работает просто отлично:

template <typename T>
void print(T&& x)
{
    std::cout << x << std::endl;
}

template <typename T0, typename... T>
void print(T0&& x, T&&... rest)
{
    std::cout << x << std::endl;
    print(std::forward<T>(rest)...);
}

Как и:

void print() { }

template <typename T0, typename... T>
void print(T0&& x, T&&... rest)
{
    std::cout << x << std::endl;
    print(std::forward<T>(rest)...);
}
...