随着if constexpr
中c++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之前的等效解决方案相同?
答案 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 ++标准明确要求它们之间在运行时,代码大小或内存方面存在任何差异。