如果 constexpr 与 sfinae

if constexpr vs sfinae

提问人:francesco 提问时间:1/7/2019 最后编辑:Barryfrancesco 更新时间:1/7/2019 访问量:3548

问:

随着 in 的引入,一些使用编译时 SFINAE in / 解决的问题现在可以使用 ,语法更简单。if constexprc++17c++14c++11if 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;
}

该例程使用 中的 SFINAE 技术,而使用 来执行相同的工作。print_sfinaec++11print_ifconstexprif constexpr

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

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

C++ C++17 SFINAE

评论

2赞 HolyBlackCat 1/7/2019
不是你问的,但如果你使用 C++17 折叠表达式,variadic 可以写成非递归单行。在 C++17 之前,同样的事情也可以使用虚拟数组技巧来完成。print
0赞 Walter 1/7/2019
@HolyBlackCat 虚拟数组技巧 = ?
1赞 Jarod42 1/7/2019
@Walter:int dummy[] = {0, ((std::cout << xs << std::endl), 0)...}; static_cast<void>(dummy); // Avoid warning for unused var

答:

10赞 Barry 1/7/2019 #1

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

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

如果语句的形式是 ,则条件的值应为上下文转换的常量表达式 ;这种形式称为 constexpr if 语句。如果转换条件的值为 ,则第一个子语句是丢弃语句,否则第二个子语句(如果存在)是丢弃语句。在封闭模板化实体的实例化过程中,如果条件在实例化后不依赖于值,则不会实例化丢弃的子语句(如果有)。ifif constexprboolfalse

这里的重点是 discard 语句没有被实例化 - 这是作为语言功能背后的全部目的,允许你编写: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)...);
    }
}

你不能用一个简单的 来做到这一点,因为这仍然需要实例化子语句 - 即使条件可以被确定为在编译时。一个简单的需要能够调用 .iffalseifprint_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)...);
}
0赞 Yakk - Adam Nevraumont 1/7/2019 #2

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

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

复制引用、调用接受引用并返回 void 的函数都是不可观察的行为。

这两个函数具有相同的可观察行为。因此,C++ 标准对它们之间在运行时、代码大小或内存使用方面的任何差异都有说明。