constexpr深度限制与clang(constexpr-depth似乎不起作用)

时间:2014-07-05 23:42:13

标签: c++ c++11 clang constexpr c++14

有没有配置constexpr实例化深度? 我正在运行-fconstexpr-depth = 4096(使用clang / XCode)。

但仍然无法编译此代码并出现错误: Constexpr变量fib_1必须由常量表达式初始化。 无论是否设置选项-fconstexpr-depth = 4096,代码都会失败。

这是clang的错误还是预期会以这种方式运行。 注意:这在fib_cxpr(26)之前有效,27是在它开始失败时。

代码:

constexpr int fib_cxpr(int idx) {
    return idx == 0 ? 0 :
           idx == 1 ? 1 :
           fib_cxpr(idx-1) + fib_cxpr(idx-2); 
}

int main() {
    constexpr auto fib_1 = fib_cxpr(27);
    return 0; 
}

3 个答案:

答案 0 :(得分:17)

TL; DR:

对于clang,您需要命令行参数-fconstexpr-steps=1271242,并且您不需要超过-fconstexpr-depth=27


计算Fibonacci数的递归方法不需要非常多的递归深度。 fib(n)所需的深度实际上不超过n。这是因为最长的呼叫链是通过fib(i-1)递归调用。

constexpr auto fib_1 = fib_cxpr(3); // fails with -fconstexpr-depth=2, works with -fconstexpr-depth=3
constexpr auto fib_1 = fib_cxpr(4); // fails with -fconstexpr-depth=3, works with -fconstexpr-depth=4

因此,我们可以得出结论,-fconstexpr-depth不是重要的设置。

此外,错误消息也表明存在差异:

constexpr auto fib_1 = fib_cxpr(27);

使用-fconstexpr-depth=26编译,为了确保达到该限制,clang会生成以下消息:

note: constexpr evaluation exceeded maximum depth of 26 calls

但是使用足够深度的-fconstexpr-depth=27进行编译会生成消息:

note: constexpr evaluation hit maximum step limit; possible infinite loop?

所以我们知道clang区分了两个失败:递归深度和'步长限制'。


“clang maximum step limit”的最高Google结果导致有关实现此功能的clang补丁的页面,包括命令行选项的实现:-fconstexpr-steps。进一步搜索此选项表示没有用户级文档。

所以没有关于clang被视为“步骤”的文档或者fib(27)所需的“步骤”数量。我们可以把它设置得很高,但我认为这是一个坏主意。相反,一些实验表明:

n : steps
0 : 2
1 : 2
2 : 6
3 : 10
4 : 18

表示步骤(fib(n))==步骤(fib(n-1))+步骤(fib(n-2))+ 2.根据此计算,{{1应该需要1,271,242个clang的步骤。所以用fib(27)进行编译应该允许程序编译,这确实是it does。使用-fconstexpr-steps=1271242进行编译会导致错误与之前相同,因此我们知道我们有一个确切的限制。

另一种不太精确的方法是从补丁中观察默认的步长限制为1,048,576(2 20 ),这显然足以满足-fconstexpr-steps=1271241。直觉上,加倍应该是充足的,从早期的分析我们知道200万是充足的。一个严格的限制是⌈φ·步骤(fib(26))⌉(恰好恰好是1,271,242)。


另外需要注意的是,这些结果清楚地表明clang没有对constexpr评估进行任何记忆。 GCC does,但似乎根本没有在clang中实现。虽然memoization会增加内存需求,但有时候,就像在这种情况下,大大减少了评估所需的时间。我从中得出的两个结论是,编写constexpr代码需要对良好的编译时进行memoization对于可移植代码来说不是一个好主意,并且可以通过支持constexpr memoization和命令行选项来启用/禁用它。

答案 1 :(得分:0)

您还可以重构您的 Fibonacci 算法,以包含可在 clang 中工作的显式记忆。

// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0

#include <iostream>

template <int idx>
constexpr int fib_cxpr();

// This constexpr template value acts as the explicit memoization for the fib_cxpr function.
template <int i>
constexpr int kFib = fib_cxpr<i>();

// Arguments cannot be used in constexpr contexts (like the if constexpr),
// so idx is refactored as a template value argument instead.
template <int idx>
constexpr int fib_cxpr() {
    if constexpr (idx == 0 || idx == 1) {
        return idx;
    } else {
        return kFib<idx-1> + kFib<idx-2>;
    }      
}

int main() {
    constexpr auto fib_1 = fib_cxpr<27>();
    std::cout << fib_1 << "\n";
    return 0; 
}

此版本适用于 fib_cxpr 的任意输入,并且只需 4 步即可编译。 https://godbolt.org/z/9cvz3hbaE

这不是直接回答问题,但我显然没有足够的声誉将其添加为评论...

答案 2 :(得分:0)

与“深度限制”无关,但与斐波那契数计算密切相关。

递归可能是错误的方法,不需要。

有一种超快的解决方案,可以实现低内存占用。

因此,我们可以对适合 64 位值的所有斐波那契数进行编译时预计算。

斐波那契数列的一个重要属性是值呈指数级增长。因此,所有现有的内置整数数据类型都会很快溢出。

使用 Binet's formula,您可以计算出第 93 个斐波那契数是最后一个适合 64 位无符号值的数。

在编译期间计算 93 个值是一项非常简单的任务。

我们首先将计算斐波那契数的默认方法定义为 constexpr 函数:

// Constexpr function to calculate the nth Fibonacci number
constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
    // Initialize first two even numbers 
    unsigned long long f1{ 0 }, f2{ 1 };

    // calculating Fibonacci value 
    while (index--) {
        // get next value of Fibonacci sequence 
        unsigned long long f3 = f2 + f1;
        // Move to next number
        f1 = f2;
        f2 = f3;
    }
    return f2;
}

这样,可以在编译时轻松计算斐波那契数列。然后,我们用所有斐波那契数填充 std::array。我们还使用 constexpr 并使其成为带有可变参数包的模板。

我们使用 std::integer_sequence 为指数 0,1,2,3,4,5, .... 创建一个斐波那契数。

这很直接,并不复杂:

template <size_t... ManyIndices>
constexpr auto generateArrayHelper(std::integer_sequence<size_t, ManyIndices...>) noexcept {
    return std::array<unsigned long long, sizeof...(ManyIndices)>{ { getFibonacciNumber(ManyIndices)... } };
};

该函数将输入一个整数序列 0,1,2,3,4,... 并返回一个 std::array<unsigned long long, ...> 和相应的斐波那契数列。

我们知道最多可以存储 93 个值。因此我们创建了一个 next 函数,它将使用整数序列 1,2,3,4,...,92,93 调用上面的函数,如下所示:

constexpr auto generateArray() noexcept {
    return generateArrayHelper(std::make_integer_sequence<size_t, MaxIndexFor64BitValue>());
}

现在,终于,

constexpr auto FIB = generateArray();

会给我们一个名为 FIB 的编译时 std::array<unsigned long long, 93>,其中包含所有的斐波那契数。如果我们需要第 i 个斐波那契数,那么我们可以简单地写成 FIB[i]。运行时不会进行计算。

我不认为有更快的方法来计算第 n 个斐波那契数。

请查看下面的完整程序:

#include <iostream>
#include <array>
#include <utility>
// ----------------------------------------------------------------------
// All the following will be done during compile time

// Constexpr function to calculate the nth Fibonacci number
constexpr unsigned long long getFibonacciNumber(size_t index) {
    // Initialize first two even numbers 
    unsigned long long f1{ 0 }, f2{ 1 };

    // calculating Fibonacci value 
    while (index--) {
        // get next value of Fibonacci sequence 
        unsigned long long f3 = f2 + f1;
        // Move to next number
        f1 = f2;
        f2 = f3;
    }
    return f2;
}
// We will automatically build an array of Fibonacci numberscompile time
// Generate a std::array with n elements 
template <size_t... ManyIndices>
constexpr auto generateArrayHelper(std::integer_sequence<size_t, ManyIndices...>) noexcept {
    return std::array<unsigned long long, sizeof...(ManyIndices)>{ { getFibonacciNumber(ManyIndices)... } };
};

// Max index for Fibonaccis that for in an 64bit unsigned value (Binets formula)
constexpr size_t MaxIndexFor64BitValue = 93;

// Generate the required number of elements
constexpr auto generateArray()noexcept {
    return generateArrayHelper(std::make_integer_sequence<size_t, MaxIndexFor64BitValue>());
}

// This is an constexpr array of all Fibonacci numbers
constexpr auto FIB = generateArray();
// ----------------------------------------------------------------------

// Test
int main() {

    // Print all possible Fibonacci numbers
    for (size_t i{}; i < MaxIndexFor64BitValue; ++i)

        std::cout << i << "\t--> " << FIB[i] << '\n';

    return 0;
}

使用 Microsoft Visual Studio Community 2019 版本 16.8.2 进行开发和测试。

另外用clang11.0和gcc10.2编译和测试

语言:C++17