如果constexpr vs sfinae

时间:2019-01-06 20:06:25

标签: c++ c++17 sfinae

随着if constexprc++17的引入,{{1} / c++14中使用编译时SFINAE解决的一些问题现在可以使用{{1 }},语法更简单。

请考虑以下编译时递归的基本示例,以生成一个打印可变数量的参数的子例程。

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; } 中的SFINAE技术,而print_sfinae通过使用c++11完成相同的工作。

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

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

2 个答案:

答案 0 :(得分:8)

  

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

该标准从[stmt.if]中指定:

  

如果if语句的格式为if constexpr,则条件的值应为上下文转换为类型bool的常量表达式;这种形式称为 constexpr if 语句。如果转换后的条件的值为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)...);
}

答案 1 :(得分:0)

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

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

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

这两个函数具有相同的令人讨厌的行为。因此,C ++标准明确要求它们之间在运行时,代码大小或内存方面存在任何差异。