if constexpr vs sfinae

if constexpr vs sfinae

本文关键字:sfinae vs constexpr if      更新时间:2023-10-16

随着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的解决方案与基于前c++17 SFINAE的等效解决方案是否相同?

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

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

如果if语句的形式为if constexpr,则条件的值应为类型为bool的上下文转换的常量表达式;这种形式被称为constexprif语句。如果转换后的条件的值是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。一个简单的CCD_ 22将需要调用CCD_。

除非rest...中有内容,否则if constexpr不会实例化递归调用,因此这是有效的。

其他一切都源于缺乏实例化。不能为丢弃的语句生成任何代码。

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++指定程序可观察的行为。

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

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

这两个函数具有相同的可观测行为。因此,C++标准在运行时、代码大小或内存使用方面没有任何区别。