如果 constexpr 与 sfinae

if constexpr vs sfinae

随着 if constexprc++17 中的引入,一些在 c++14/c++11 中使用编译时 SFINAE 解决的问题现在可以使用 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 使用来自 c++11 的 SFINAE 技术,而 print_ifconstexpr 使用 if constexpr.

完成相同的工作

是否可以假设编译器在评估 if constexpr 时完全丢弃未验证的条件并仅为满足 if constexpr 条件的分支生成代码?标准是否为编译器指定了这种行为?

更一般地说,就效率和生成的代码而言,基于 if constexpr 的解决方案是否与基于 pre-c++17 SFINAE 的等效解决方案相同?

Can one assume that the compiler, on evaluating the if constexpr completely discards the non-verified condition and generate code only for the branch which satisfy the if constexpr condition? Does the standard specifies such a behavior for the compiler?

标准规定,从[stmt.if]:

If the if statement is of the form if constexpr, the value of the condition shall be a contextually converted constant expression of type bool; this form is called a constexpr if statement. If the value of the converted condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity, if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.

这里的要点是丢弃语句未实例化 - 这是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)...);
}

C++ 指定程序可观察的行为。

两段代码都有可观察到的行为打印。

复制引用、调用采用引用的函数和 return void,都是不可观察的行为。

这两个函数具有相同的可观察行为。所以 C++ 标准对它们之间在运行时、代码大小或内存使用方面的任何差异毫无意义。