【问题标题】:compile time templated C++ calculations on unsigned long long ? on doubles?在 unsigned long long 上编译时模板化 C++ 计算?双打?
【发布时间】:2011-01-05 20:17:56
【问题描述】:

我使用编译时 int powers 计算 Power,来计算 n**p。它使用整数。 我想计算比 int 更大的东西,好吗? 它适合 u64(无符号长长)。 C++ 模板可以在 u64 上进行编译时计算吗?枚举无法做到这一点。 双打?可能吗?

我真的希望类型是模板的 arg。可能吗 ? 我的编译器不是 c++0x。

谢谢 安德烈

template<int N, int P> struct Power {
   enum { val = N * Power<N, P-1>::val };
};

template<int N> struct Power<N, 0> {
   enum { val = 1 };
};

int result = Power<2, 5>; // 2**5 == 32

【问题讨论】:

  • 对于那些在今年读过的人+ - C++11 和更新版本支持 constexpr,它更简单、更漂亮,并且做同样的工作。

标签: c++ templates metaprogramming


【解决方案1】:

是的,您可以对任何原始整数类型进行编译时计算。但是,您不能对浮点值进行计算,因为模板不能在这些值上进行参数化。即将推出的 C++0x 标准将引入特殊类来执行编译时有理算术,因此您可以根据需要使用它。

【讨论】:

  • 你是说在pre-c0x c++中,这是不可能的,对吧。
  • 在当前的 C++ 标准中可以使用整数类型。如果您想为此投入精力,您可以构建编译时有理算术库,以便它们可以与当前的 C++ 一起使用——它们不使用任何特殊的语言特性——但这项工作已经为你完成了在 C++0x 中。而且 C++0x 和当前的 C++ 都不支持带有实值参数的模板。
【解决方案2】:

为了扩展 sbi 的实现,这里有一个通过平方求幂(它需要更少的模板实例化来实现大幂)。

请注意,如果您真的只想计算 2 的幂,最好只左移(即2**x == 1 &lt;&lt; x)而不是做所有这些模板的东西。

#include <iostream>

template <unsigned long long N, unsigned int P, int Odd = (P&1)> struct Power;

template <unsigned long long N, unsigned int P>
struct Power<N,P,0> { // even (square N and halve the power)
    static const unsigned long long val = Power<N*N,(P/2)>::val;
};

template <unsigned long long N, unsigned int P>
struct Power<N,P,1> { // odd (multiply by N and decrement the power)
    static const unsigned long long val = N * Power<N,(P-1)>::val;
};

template <unsigned long long N>
struct Power<N,0,0> { // zero (x**0 is 1 for all x != 0)
    static const unsigned long long val = 1;
};

int main() {
    std::cout << "2**0 = " << Power<2,0>::val << "\n";
    std::cout << "2**1 = " << Power<2,1>::val << "\n";
    std::cout << "2**2 = " << Power<2,2>::val << "\n";
    std::cout << "2**3 = " << Power<2,3>::val << "\n";
    std::cout << "2**4 = " << Power<2,4>::val << "\n";
    std::cout << "2**5 = " << Power<2,5>::val << "\n";
    std::cout << "2**6 = " << Power<2,6>::val << "\n";
    std::cout << "2**7 = " << Power<2,7>::val << "\n";
    std::cout << "2**8 = " << Power<2,8>::val << "\n";
    std::cout << "2**9 = " << Power<2,9>::val << "\n";
    std::cout << "2**10 = " << Power<2,10>::val << "\n";
    std::cout << "2**11 = " << Power<2,11>::val << "\n";
    std::cout << "2**12 = " << Power<2,12>::val << "\n";
    std::cout << "2**30 = " << Power<2,30>::val << "\n";
    std::cout << "2**40 = " << Power<2,40>::val << "\n";
    std::cout << "2**50 = " << Power<2,50>::val << "\n";
    std::cout << "2**60 = " << Power<2,60>::val << "\n";
    return 0;
}

免责声明:我没有声称由于模板实例化数量的减少,这段代码一定会编译得更快(尽管它可能会)。我真的只是把它写成一个玩具演示。我将其作为练习留给读者编写一个版本,该版本在使用Power&lt;&gt; 时不会被显式传递Odd 参数值的人所攻击。

【讨论】:

  • template &lt;unsigned long long N, unsigned int P&gt; struct Power { enum { Odd = P&amp;1 }; static const unsigned long long val = (Odd?N:1)*Power&lt;N*N,(P/2)&gt;::val; } -- 模板减少 33%,代码减少一半,Odd 已删除错误。
【解决方案3】:

您可以使用多精度数学来计算大数(在下面的示例中,我使用带有 3 个模板参数的 96 位计算,您可以使用任何常数)。您需要有多个整数作为模板参数。

在执行编译时乘法时,您可能应该将 32 位数字相乘,得到 64 位结果;结果应该被分成2个模板参数。

溢出检查可能是可能的,但可能很棘手。

const uint64_t W = 1000000000; // word size: 2^32 is best; any smaller number is OK
// I use a power of 10 as word size for ease of printing (see main() below)

// The following class performs multiplication of (n0 + W*n1 + W*W*n2) by (base)
template <unsigned n0, unsigned n1, unsigned n2, uint64_t base, unsigned p> class power_temp
{
    typedef power_temp<
        n0 * base % W,
        n1 * base % W + n0 * base / W,
        n2 * base % W + n1 * base / W,
        base, p - 1> mult_type;
public:
    static const unsigned x0 = mult_type::x0;
    static const unsigned x1 = mult_type::x1;
    static const unsigned x2 = mult_type::x2;
};

// The following partial specialization is used to end recursion
template <unsigned n0, unsigned n1, unsigned n2, uint64_t base>
class power_temp<n0, n1, n2, base, 0>
{
public:
    static const unsigned x0 = n0;
    static const unsigned x1 = n1;
    static const unsigned x2 = n2;
};

// The following class calculates a power, using compile-time calculations
template <unsigned base, unsigned p> struct power
{
    static const unsigned x0 = power_temp<1, 0, 0, base, p>::x0;
    static const unsigned x1 = power_temp<1, 0, 0, base, p>::x1;
    static const unsigned x2 = power_temp<1, 0, 0, base, p>::x2;
};

int main()
{
    typedef power<123456789, 3> my1;
    printf("%09d%09d%09d\n", my1::x2, my1::x1, my1::x0);

    typedef power<5, 33> my2;
    printf("%09d%09d%09d\n", my2::x2, my2::x1, my2::x0);
}

【讨论】:

    【解决方案4】:
    template<int N, unsigned int P> struct Power {
       static const unsigned long long val = N * Power<N, P-1>::val;
    };
    
    template<int N> struct Power<N, 0> {
       static const unsigned long long val = 1;
    }
    

    请注意,我将P 设为unsigned int,因为您的模板会因负值而失败。

    【讨论】:

    • 不编译 pre-c0x。
    • @Andrei:为什么不呢? VC9 和Comeau Online 都接受它。
    • @Andrei:您现在已经使用了两次“pre-c0x”,所以我认为这不是错字。 C++ 的下一个(也是第 3 个)版本称为 C++0x,因此您应该使用“pre-C++0x”来指代以前的版本。 C0x 也可能与C1X 混淆,后者是 C 标准的下一个版本,我以前见过它称为 C0X。
    • @Fred:嗯,至少这会使 Andrei 的评论属实。代码不会用当前的 C 编译器编译。 :)
    • @sbi: 是的,我不认为他可能真的在谈论 C。:)
    猜你喜欢
    • 2021-02-03
    • 1970-01-01
    • 1970-01-01
    • 2016-07-29
    • 2013-09-01
    • 2020-01-12
    • 2018-10-20
    • 2015-08-22
    • 1970-01-01
    相关资源
    最近更新 更多