if vs if constexpr inside constexpr 函数

if vs if constexpr inside constexpr function

最近我在我的 constexpr 函数中将一些 if constexpr 修改为 if,发现它们仍然可以正常工作,并且可以在编译时进行评估。这是最小情况:

template<int N>
constexpr bool is_negative()
{
    if constexpr  (N >= 0) return false;
    else  return true; 
}
int main()
{
    constexpr  bool v = is_negative<1>();
}

live demo

在上面的例子中,N 必须在编译时知道,因为它是非类型模板参数,所以 if constexpr 在这里工作得很好。但是,它是一个 constexpr 函数,因此,iirc,即使我将 if constexpr 替换为 if,也有 可能 获得 return 值:

template<int N>
constexpr bool is_negative()
{
    if  (N >= 0) return false;
    else  return true; 
}
int main()
{
    constexpr  bool v = is_negative<1>();
}

live demo

cppref开始,A constexpr function must satisfy the following requirements:中的所有要求都没有提到if。因此,IIUC,constexpr 函数是否包含要在编译时求值的 if 应该是实现定义的行为,即使所有相关变量在编译时都是已知的(如上面的 is_negative)。

所以,我的结论是:

以上是我的个人想法,可能有些重要missed/misunderstand,欢迎指正。问题仍然没有改变:ifif constexpr,对于预期在编译时求值的 constexpr 函数,应该优先使用。

参考资料: - -

Before c++17, we don't have if constexpr, so the choice is if, which means it is not guaranteed to get our constexpr functions get evaluted at compile time, all depend on compiler implementation

if 语句不是 constexpr 的事实并不意味着它不能作为 constexpr 表达式的一部分在编译时求值。在您的示例中, v 在两种情况下都在编译时进行评估,因为它必须是:它是一个常量表达式。那不是实现定义的。

After c++17, if constexpr is prefered if we want constexpr functions get evaluated at compile time.

引入 constexpr if 语句来解决问题。让 constexpr 函数在编译时得到评估不是那个问题。

这是一个示例,其中需要 constexpr if 而不是简单的 if(取自 cppreference):

template <typename T>
auto get_value(T t) {
    if constexpr(std::is_pointer_v<T>)
        return *t; // deduces return type to int for T = int*
    else
        return t;  // deduces return type to int for T = int
}

尝试删除 constexpr 关键字,看看会发生什么 (demo)。

此外,请注意,您始终可以使用其他方法解决该问题,但 if constexpr 具有简洁的优点。例如,这里有一个等效的 get_value 使用标签调度:

template<typename T>
auto get_value_impl(T t, std::true_type) {
    return *t;
}
template<typename T>
auto get_value_impl(T t, std::false_type) {
    return t;
}

template<typename T>
auto get_value(T t) {
    return get_value_impl(t, std::is_pointer<T>{});
}

Demo

if constexpr和if的区别在于表达式是否总能在编译时执行。在您的示例中,您使用的是模板参数,因此编写哪个模板参数并不重要。如果您有以下代码,可以注意到差异:

constexpr bool is_negative(int n)
{
    if  (n >= 0) return false;
    else  return true; 
}
int main(int argc, char**)
{
    constexpr  bool v = is_negative(1);
    bool b = is_negative(argc);
    return static_cast<int>(v || b);
}

对于上面的代码,写if constexpr是不行的。因为您可以使用运行时值调用该函数。

同样,这应该无关紧要,因为两个代码路径都有效。当使用具有常量值的函数时,正常的编译器优化应该开始。

if constexpr 真正有趣的是只有一条路径有效:

template <typename T>
constexpr auto get_value(T t) {
    if constexpr(std::is_pointer_v<T>)
        return *t; // deduces return type to int for T = int*
    else
        return t;  // deduces return type to int for T = int
}

如果 T 是一个整数,带有 *t 的代码路径是无效的,因为您不能取消引用一个整数。但是,因为使用 if constexpr 而不是 if,如果错误路径中的代码依赖于模板参数,则只需要在语法上是正确的。

当您搜索指南时,编译器已经要求:当其中一个代码路径无效时使用 if constexpr。根据参数使用 if

对于 if 条件在编译时可通过 2 个有效路径计算的情况,使用 if constexpr 要求即使在调试版本中也对其进行优化,如果你想使用 if在调试版本中逐步完成它。

如果你走极端,表达式可能会变得过于复杂,编译器无法在生产构建中对其进行优化,在这种情况下,if constexpr 在热路径中可能会再次变得有趣。就个人而言,我还没有遇到过这种情况,但是我没有那么多地使用它。