非依赖条件的条件 constexpr

Conditional constexpr on non-dependent condition

假设我有一个配置函数要由库的用户定义,它可能是也可能不是 constexpr

constexpr int iterations() { return 10; }
// Or maybe:
int iterations() { return std::atoi(std::getenv("ITERATIONS")); }

// I'm okay with requiring the constexpr version to be:
constexpr auto iterations() { return std::integral_constant<int, 10>{}; }

现在我有一个函数,它根据 iterations() 的值有不同的行为,但是如果 iterations 是,这个函数应该是 constexpr,因为我希望用户是能够在 constexpr 中使用它,如果他们将库配置为允许它的话:

/*constexpr*/ std::uint32_t algorithm(std::uint32_t x) {
    auto const iterations = ::iterations();

    // For illustration purposes
    for (int i = 0; i < iterations; ++i) {
        x *= x;
    }

    return x;
}

如果 iterations() 是,我可以对 algorithm 做些什么来实现函数 constexpr?简而言之,我想要 constexpr(constexpr(::iterations())).

这样的东西

请注意,“条件 constexpr”通常取决于模板参数,如 in which case the constexpr keyword can just be used, but in this case, I want the constexpr to be conditional on something which is not a template parameter, but a statically known function. Marking algorithm as constexpr is a compilation error:

error: call to non-'constexpr' function 'bool iterations()'

I'm okay with requiring the constexpr version to be: ... std::integral_constant<int, 10>

因为您可以要求函数的 constexpr 版本具有不同的 return 类型,我们可以检测到这种特殊类型,并通过使用约束(C++20 的 requires)。请注意,我们必须进一步将主体包裹在 if constexpr 中,因为编译器仍会检查函数的主体:

template <typename T>
constexpr bool is_integral_constant = false;

template <typename T, T value>
constexpr bool is_integral_constant<std::integral_constant<T, value>> = true;

constexpr std::uint32_t algorithm_impl(std::uint32_t x, int iterations) {
    for (int i = 0; i < iterations; ++i) {
        x *= x;
    }

    return x;
}

constexpr std::uint32_t algorithm(std::uint32_t x)
        requires is_integral_constant<decltype(::iterations())> {
    if constexpr (is_integral_constant<decltype(::iterations())>) {
        return ::algorithm_impl(x, ::iterations());
    } else {
        // Unreachable, but enough to convince the compiler that there is a
        // constexpr friendly path through the function
        return 0xDEADBEEF;
    }
}

std::uint32_t algorithm(std::uint32_t x) {
    return ::algorithm_impl(x, ::iterations());
}

Demo

从您的回答中汲取灵感,您可以使用概念来强加 iteration() return 一个整数常数。

重要的是记住 algorithm() ha 成为模板函数或概念 (requires) 是行不通的,所以你可以强加一个未使用的默认模板参数

//VVVVVVVVVVVVVVVVVVVVVVVVVV <-- add this to activate `requires`  
  template <typename = void>
  constexpr std::uint32_t algorithm(std::uint32_t x)
          requires is_integral_constant<decltype(::iterations())>
   { return ::algorithm_impl(x, ::iterations()); }

以下是你的例子的简化

template <typename T>
constexpr bool is_integral_constant = false;

template <typename T, T value>
constexpr bool is_integral_constant<std::integral_constant<T, value>> = true;

constexpr std::uint32_t algorithm_impl(std::uint32_t x, int iterations) {
    for (int i = 0; i < iterations; ++i) {
        x *= x;
    }

    return x;
}

template <typename = void>
constexpr std::uint32_t algorithm(std::uint32_t x)
        requires is_integral_constant<decltype(::iterations())>
 { return ::algorithm_impl(x, ::iterations()); }

std::uint32_t algorithm(std::uint32_t x) {
    return ::algorithm_impl(x, ::iterations());
}

您可以通过确保存在 一些 组模板参数和函数参数来消除编译器诊断,即使 [=11] 对您的函数的调用是常量表达式=] 是 不是 常量表达式。例如:

template <bool True = true>
constexpr std::uint32_t algorithm(std::uint32_t x) {
    if constexpr (True) {
        auto const iterations = ::iterations();

        for (int i = 0; i < iterations; ++i) {
            x *= x;
        }

        return x;
    } else {
        return 0;
    }
}

algorithm<false>(meow) 是常量表达式,只要 meow 是,所以编译器不会抱怨 (https://godbolt.org/z/GvE9ME)。