“ if conexpr()”与“ if()”的区别

if constexpr()if()有什么不同?

何时何地我可以同时使用它们?

32061 次浏览

if的普通声明:

  • 每次控制到达它时,是否评估它的状态,如果有的话
  • 确定执行两个子语句中的哪一个,跳过另一个
  • 要求两个子语句都是格式良好的,而不管在运行时实际选择哪个子语句

if constexpr声明:

  • 在提供了所有必要的模板参数之后,是否在编译时计算其条件
  • 确定要编译两个子语句中的哪一个,放弃另一个
  • 不要求丢弃的子语句格式良好

唯一的区别是,if constexpr是在编译时计算的,而 if不是。这意味着分支可以在编译时被拒绝,因此永远不会被编译。


假设您有一个函数 length,它返回一个数字的长度,或者返回一个具有 .length()函数的类型的长度。你不能在一个函数中完成,编译器会抱怨:

template<typename T>
auto length(const T& value) noexcept {
if (std::integral<T>::value) { // is number
return value;
else
return value.length();
}


int main() noexcept {
int a = 5;
std::string b = "foo";


std::cout << length(a) << ' ' << length(b) << '\n'; // doesn't compile
}

错误信息:

main.cpp: In instantiation of 'auto length(const T&) [with T = int]':
main.cpp:16:26:   required from here
main.cpp:9:16: error: request for member 'length' in 'val', which is of non-class type 'const int'
return val.length();
~~~~^~~~~~

这是因为当编译器实例化 length时,函数看起来是这样的:

auto length(const int& value) noexcept {
if (std::is_integral<int>::value) { // is number
return value;
else
return value.length();
}

valueint,因此没有 length成员函数,所以编译器会抱怨。编译器不能看到对于 int永远不会到达这个语句,但是这没有关系,因为编译器不能保证这一点。

现在,您可以对 length进行专门化,但是对于许多类型(如本例中的每个数字和具有 length成员函数的类) ,这会导致大量重复代码。SFINAE 也是一种解决方案,但是它需要多个函数定义,这使得代码比下面所需的要长得多。

使用 if constexpr而不是 if意味着分支(std::is_integral<T>::value)将在编译时得到计算,如果它是 true,那么其他分支(else ifelse)将被丢弃。如果是 false,则检查下一个分支(这里是 else) ,如果是 true,则丢弃所有其他分支,以此类推..。

template<typename T>
auto length(const T& value) noexcept {
if constexpr (std::integral<T>::value) { // is number
return value;
else
return value.length();
}

现在,当编译器实例化 length时,它看起来像这样:

int length(const int& value) noexcept {
//if (std::is_integral<int>::value) { this branch is taken
return value;
//else                           discarded
//    return value.length();     discarded
}


std::size_t length(const std::string& value) noexcept {
//if (std::is_integral<int>::value) { discarded
//    return value;                   discarded
//else                           this branch is taken
return value.length();
}

因此这两个重载是有效的,代码将成功编译。