GCC 5.3.1 c++在编译可变模板时出现故障

GCC 5.3.1 C++ Stalls When Compiling Variadic Template

本文关键字:故障 c++ 编译 GCC      更新时间:2023-10-16

我写了以下代码:

#include<array>
#include<type_traits>
namespace math{
    namespace detail{
        template<std::size_t... Is> struct seq{};
        template<std::size_t N, std::size_t... Is>
        struct gen_seq : gen_seq<N-1, N-1, Is...>{};
        template<std::size_t... Is>
        struct gen_seq<0, Is...> : seq<Is...>{};
        template<class T,std::size_t N>
        struct sin_coeffs{
            using array_type = std::array<T,N>;
            constexpr static inline T coeff(std::size_t n){
                return power(-1, n-1) * inverse((T)factorial((2 * n)-1));
            }
            template<std::size_t...NS>
            constexpr static array_type _coeffs(seq<NS...>){
                return {{coeff(NS)...}};
            }
            constexpr static array_type coeffs=_coeffs(gen_seq<N>{});
        };
    }
    template<class T,std::size_t N = max_factorial, class dcy = std::decay_t<T>>
    constexpr std::enable_if_t<std::is_floating_point<dcy>::value,dcy> sin(T x) noexcept{
        constexpr std::array<dcy,N>& coeffs = detail::sin_coeffs<dcy,N>::coeffs;
        const dcy x_2 = x*x;
        dcy pow = x;
        dcy result = 0;
        for(std::size_t i=0;i<N;++i){
            result += coeffs[i] * pow;
            pow*=x_2;
        }
        return result;
    }
}

主要例子:

int main()
{
    constexpr double d = math::sin(0.0);
}

代码被设计成一个constexpr sin函数,它使用constexpr数组保存系数来进行所需的计算。

未列出的所有函数都出现在一个单独的头文件中,并且编译没有问题。

我正在尝试使用"索引"技巧来填充数组,使用constexpr函数基于对另一个问题的回答。

我正在编译带有标志符

的GCC 5.3.1

——std=c++1z -pthread -g -O3 -MMD -MP -Wall -pedantic

编译器没有向我的代码发出错误,但在编译时停止。

我让编译运行了几分钟,但它没有做任何事情。

我已经测试了代码中使用的所有函数,它们都独立于这一节编译得很好。

int main()
{
    math::detail::sin_coeffs<double,20>::coeffs[0];
}

这个代码片段也重现了这个问题,使我相信它与sin函数本身无关,而是与sin_coeffs结构体有关。

编辑:以下是所请求的其他函数:

#include <type_traits>
namespace math{
    template<class T,class dcy = std::decay_t<T>>
    constexpr inline std::enable_if_t<std::is_floating_point<T>::value,dcy> inverse(T value){
        return (value == 0) ? 0.0 : 1.0 / value;
    }
    template <class T>
    constexpr inline std::decay_t<T> sign(T value) {
        return value < 0 ? -1 : 1;
    }
    template <typename T>
    constexpr inline std::decay_t<T> abs(T value) {
        return value * sign(value);
    }
    template<class T>
    constexpr inline std::decay_t<T> power(T const& base, std::size_t const& pow){
        if(pow==0){return 1;}
        else if(pow == 1){return base;}
        else{
            T result = base;
            for(std::size_t i=1;i<pow;++i){
                result*=base;
            }
            return result;
        }
    }
    constexpr std::intmax_t factorial(std::intmax_t const& n){
        if(n==0){return 1;}
        std::intmax_t result = n;
        for(std::intmax_t i=n-1;i>0;--i){
            result *=i;
        }
        return result;
    }
    constexpr static std::size_t max_factorial = 20;//replace with calculated version later
}

如何修复你的代码?

  1. 这里缺少一个const限定符:
constexpr const std::array<dcy,N>& coeffs = /* ... */ ;
          ^^^^^
  • 你的gen_seq生成从0N - 1的值,但是关于你的coeff函数,你想要从1N的值。您可以通过以下方式修复:
    • 更改基本模板以生成从1N的值(参见此答案的底部以获得详细解释):
    template<std::size_t N, std::size_t... Is>
    struct gen_seq: gen_seq<N-1, N, Is...> {};
    //                           ^--- N instead of N - 1
    
    • 改变你叫coeff的方式(感谢@ tc。,见注释):
    template<std::size_t...NS>
    constexpr static array_type _coeffs(seq<NS...>){
        return {{coeff(NS + 1)...}};
    //                   ^^^^
    }
    
  • 你不应该使用power来计算-1 ** n,使用三元条件:
  • constexpr static inline T coeff(std::size_t n){
        return (n%2 ? 1 : -1) * inverse((T)factorial((2 * n)-1));
    }
    

    有了这个,我可以计算系数:

    auto arr = math::detail::sin_coeffs<double, 10>::coeffs;
    for (auto x: arr) {
        std::cout << x << " ";
    }
    
    输出:

    1 -0.166667 0.00833333 -0.000198413 2.75573e-06 -2.50521e-08 1.6059e-10 -7.64716e-13 ...
    

    据我所知,这些是正确的系数(1, -1/3!, 1/5!,…)。注意,我必须使用N = 10,否则我会溢出std::intmax_t(在我的体系结构上)——如果你用factorial溢出std::intmax_t, Clang会在编译时警告你(多么好的编译器!)。

    通过上述两个修改,您的代码工作得很好(除了max_factorial值,但您可以根据自己的喜好进行调整)。

    参见rextester上的代码:http://rextester.com/CRR35028

    你的代码中的主要问题是什么?

    你的gen_seq<N>生成了一个从0N - 1的序列,所以你调用的是coeff(0),它调用的是power(-1, static_cast<size_t>(0) - 1),实际上是power(-1, 18446744073709551615)(在我的架构上),它不能编译。在power"修复"编译中添加一个微不足道的情况(显示这是一个问题,但没有解决真正的问题):

    template<class T>
    constexpr inline std::decay_t<T> power(T const& base, std::size_t const& pow) {
        if (pow == static_cast<size_t>(0) - 1) { // Stupid test
          return 1;
        }
        /* ... */
    }
    

    另外,你的max_factorial值也可能太大了。在对power进行修正后,我无法编译max_factorial > 11(我可能有32位std::intmax_t,所以你可能可以超过这个,但我认为20在所有情况下都太大了)。

    此外,对于未来的问题,clang似乎提供了更好的信息,说明为什么它不能编译:

      它对迭代次数有限制,所以我能够发现power正在做一个(几乎)无限循环。
    • 如果factorial的返回值超过intmax_t,则给出警告。

    gen_seq技巧是如何工作的?

    你的seq结构体基本上是std::size_t...的结构体持有人(因为你不能直接将其存储在变量中)。

    gen_seq是一个递归结构,它使用gen_seq<N - 1, ...>构建gen_seq<N, ...>。它是如何工作的:

    gen_seq<3>: gen_seq<2, 2>
    gen_seq<2, 2>: gen_seq<1, 1, 2>
    gen_seq<1, 1, 2>: gen_seq<0, 0, 1, 2>
    gen_seq<0, 0, 1, 2>: seq<0, 1, 2> // Specialized case
    

    可以看到,由于继承了gen_seq<N - 1, N - 1, ...>,所以从seq继承的最后一个继承具有0值,这是您不想要的。由于"发送"到seq的是可变的std::size_t...,您希望避免使用0,因此更改为gen_seq<N - 1, N, ...>:

    gen_seq<3>: gen_seq<2, 3>
    gen_seq<2, 3>: gen_seq<1, 2, 3>
    gen_seq<1, 2, 3>: gen_seq<0, 1, 2, 3>
    gen_seq<0, 1, 2, 3>: seq<1, 2, 3> // Specialized case
    

    现在,当调用_coeffs(gen_seq<N>{})时,允许编译器推导出_coeffs的模板实参NS。从这里,您可以使用_coeffs中的NS来进行包扩展:

    {{coeff(NS)...}};