【问题标题】:Is there no built-in way to compute a power at compile time in C++?在 C++ 编译时是否没有内置方法来计算功率?
【发布时间】:2015-01-31 22:42:47
【问题描述】:

我有以下非常简单的模板。据我所知,^ 不是指数运算符。现在我正在寻找一种计算这种能力的方法。网上有很多递归模板的例子。这不是太难。

但我想知道:C++ 中实际上没有“内置”方法可以在编译时计算它吗?

template <int DIM>
class BinIdx : Idx
{
        static const int SIZE = 3 ^ DIM; // whoops, this is NOT an exponential operator!
}

【问题讨论】:

  • 如果只是 2 的幂,请使用 1 &lt;&lt; DIM。否则,不。
  • 对于二的幂...1 &lt;&lt; DIM :p
  • 不一定是 2 的幂 ;-)
  • 没有。也没有内置编译时sqrt,或内置编译时exp或内置编译时log……你真的认为有必要吗?
  • 您始终可以使用一些脚本语言进行预处理和源代码生成。有时会为选项类等完成。否则,答案就短如&lt;&lt;,对于C++编程,你最好自己去查。

标签: c++ templates compile-time-constant


【解决方案1】:

不,没有通用的内置方法来计算价值的力量。有来自标准库的pow 函数,您可以使用&lt;&lt; 移位运算符来处理特殊情况2^x

这适用于您的情况 (*):

static const int SIZE = (1 << DIM);

* = 在我写完答案后,您将问题从 2^x 更新为 3^x

对于另一种特殊情况 x^y,其中 x 和 y 是静态的,您可以编写一个长乘法:

const result int = x*x*x*x*x;

【讨论】:

    【解决方案2】:

    如前所述,如果指数是 2 的幂,您可以使用 &lt;&lt;

    否则,如果指数是非负整数,您可以编写一个像这样的 constexpr 函数。

    template<typename T, typename U>
    auto constexpr pow(T base, U exponent) {
        static_assert(std::is_integral<U>(), "exponent must be integral");
        return exponent == 0 ? 1 : base * pow(base, exponent - 1);
    }
    

    不过,对于大指数和负指数,这显然会失效。

    我并不完全了解编译器如何优化常量表达式中的函数调用。这是指数为 2 的幂的情况下的手动优化。这也将减少完成的递归量。

    template<typename T>
    bool constexpr is_power_of_two(T x) {
        return (x != 0) && ((x & (x - 1)) == 0);
    }
    
    template<typename T, typename U>
    auto constexpr pow(T base, U exponent) {
        static_assert(std::is_integral<U>(), "exponent must be integral");
        if (is_power_of_two(exponent)) {
            return base << exponent;
        }
        return exponent == 0 ? 1 : base * pow(base, exponent - 1);
    }
    

    还提供更高效的算法。但是,我不擅长计算机科学,所以我不知道如何实现它们。

    【讨论】:

    • 我认为应该更改您的函数名称 - 因为std::pow >o
    • 您的最后一点很重要:如果您不小心,很容易使编译器因编译时计算而爆炸。这是在标准库中提供类似内容的充分理由
    • @ikh:你为什么这么说? std 命名空间的全部意义在于允许您为自己的函数提供合理的名称,而不会有与库名称冲突的危险。
    • @MikeSeymour 但是pow()是从C继承的。在很多情况下,我们可以看到包括&lt;math.h&gt;而不是&lt;cmath&gt;的人,这会导致事情被破坏。
    • 如果要优化,更好的优化是平方指数:en.wikipedia.org/wiki/Exponentiation_by_squaring
    【解决方案3】:

    您可以使用模板元编程。让我展示一下代码。

    template <int A, int B>
    struct get_power
    {
        static const int value = A * get_power<A, B - 1>::value;
    };
    template <int A>
    struct get_power<A, 0>
    {
        static const int value = 1;
    };
    

    用法:

    std::cout << get_power<3, 3>::value << std::endl;
    

    (live example)

    【讨论】:

    • OP 明确要求内置更直接的替代模板元编程的方法。所以这不是答案。
    • @Cheersandhth.-Alf 哦,你是对的;我没有仔细阅读问题>o
    【解决方案4】:

    作为elyse's answer的补充,这里有一个递归深度为log(n)的版本:

    template<typename T>
    constexpr T sqr(T a) {
        return a * a;
    }
    
    template<typename T>
    constexpr T power(T a, std::size_t n) {
        return n == 0 ? 1 : sqr(power(a, n / 2)) * (n % 2 == 0 ?  1 : a);
    }
    

    【讨论】:

      【解决方案5】:

      命名运算符库:

      namespace named_operator {
        template<class D>struct make_operator{
          constexpr make_operator(){}
        };
        template<class T, char, class O> struct half_apply { T&& lhs; };
      
        template<class Lhs, class Op>
        constexpr
        half_apply<Lhs, '*', Op>
        operator*( Lhs&& lhs, make_operator<Op> ) {
          return {std::forward<Lhs>(lhs)};
        }
      
        template<class Lhs, class Op, class Rhs>
        constexpr auto
        times( Lhs&& lhs, Op, Rhs&& rhs, ... ) // ... keeps this the worst option
        -> decltype( invoke( std::declval<Lhs>(), Op{}, std::declval<Rhs>() ) )
        {
          // pure ADL call, usually based off the type Op:
          return invoke( std::forward<Lhs>(lhs), Op{}, std::forward<Rhs>(rhs)     );
        }
      
        template<class Lhs, class Op, class Rhs>
        constexpr auto
        operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
        -> decltype(
          times( std::declval<Lhs>(), Op{}, std::declval<Rhs>() )
        )
        {
          return times( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
        }
      }
      

      它只支持operator*,但扩展它应该是显而易见的。为 times 等价物选择名称有点问题。

      @Anton 的解决方案,增加了命名运算符:

      namespace power {
        template<typename T>
        constexpr T sqr(T a) {
          return a * a;
        }
      
        template<typename T>
        constexpr T power(T a, std::size_t n) {
          return n == 0 ? 1 : sqr(power(a, n / 2)) * (n % 2 == 0 ?  1 : a);
        }
      
        namespace details {
          struct pow_tag {};
          constexpr named_operator::make_operator<pow_tag> pow;
      
          template<class Scalar>
          constexpr Scalar times( Scalar lhs, pow_tag, std::size_t rhs ) {
            return power( std::forward<Scalar>(lhs), rhs );
          }
        }
        using details::pow;
      }
      

      现在可以了:

      using power::pow;
      int array[ 2 *pow* 10 ] = {0};
      

      live example.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-25
        • 1970-01-01
        • 2019-03-25
        • 2011-08-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多