【问题标题】:Replacement for ternary operator in template metaprogramming模板元编程中三元运算符的替换
【发布时间】:2017-07-28 16:09:37
【问题描述】:

我正在 C++ 中实现二项式系数(n 选择 k)函数。 除了使用“正常”函数(在运行时评估)之外,这还可以使用模板元编程来完成(当参数在编译时已知时):

template <unsigned int n, unsigned int k>
struct Binomialkoeffizient {
    static const unsigned int value = Binomialkoeffizient<n, k-1>::value * (n-k+1) / k;
};

template <unsigned int n>
struct Binomialkoeffizient<n, 0> {
    static const unsigned int value = 1;
};

这个实现的缺点是,在 k > n/2 的情况下,它没有利用定理 n 选择 k = n 选择 n-k。因此可能会发生不必要的算术溢出,例如49选43会溢出,而49选6不会。

我尝试了以下方法来改善这一点:

template <unsigned int n, unsigned int k>
struct Binomialkoeffizient {
    static const unsigned int value = (2*k > n) ? Binomialkoeffizient<n, n-k>::value : Binomialkoeffizient<n, k-1>::value * (n-k+1) / k;
};

template <unsigned int n>
struct Binomialkoeffizient<n, 0> {
    static const unsigned int value = 1;
};

不幸的是,我收到了fatal error: template instantiation depth exceeds maximum of 900

这似乎是由于在递归模板实例化过程中没有评估三元运算符。

使用?: 有哪些可能的替代方法?

我对 C++11 之前的解决方案和更新的解决方案都感兴趣(也许std::enable_if 有帮助,但我不太了解)。

【问题讨论】:

  • 您是否尝试过使用std::conditional
  • 您能详细说明一下吗?我不确定如何准确使用它。它似乎也只适用于 C++11 以上。
  • @Fabian 是的,它是 C++11。第 1 步:在 C++11 中使用 std::conditional 解决它。第 2 步:用 C++03 编写 my::conditional。第 3 步:利润。
  • @Yakk 现在我终于知道什么了????是。

标签: c++ templates template-meta-programming c++03


【解决方案1】:

睡了一夜之后,我想我明白了std::conditional 的意思。

编辑:正如@Yakk 所提议的,我自己也实现了conditional

此实现适用于所有 C++ 标准:

#if __cplusplus >= 201103L
    // in C++11 and above we can use std::conditional which is defined in <type_traits>
    #include <type_traits>
    namespace my {
        using std::conditional;
    }
#else
    // in older C++ we have to use our own implementation of conditional
    namespace my {
        template <bool b, typename T, typename F>
        struct conditional {
            typedef T type;
        };

        template <typename T, typename F>
        struct conditional<false, T, F> {
            typedef F type;
        };
    }
#endif

template <unsigned int n, unsigned int k>
struct Binomialkoeffizient {
    static const unsigned int value = my::conditional< (2*k > n), Binomialkoeffizient<n, n-k>, Binomialkoeffizient<n, k> >::type::_value;
    static const unsigned int _value = Binomialkoeffizient<n, k-1>::_value * (n-k+1) / k;
};

template <unsigned int n>
struct Binomialkoeffizient<n, 0> {
    static const unsigned int value = 1;
    static const unsigned int _value = 1;
};

热烈欢迎有关如何使代码更简洁或优雅的建议(是否真的需要使用第二个静态成员_value?)。

【讨论】:

    【解决方案2】:

    C++11 引入了constexpr 说明符,

    ...(which) 声明可以评估 编译时的函数或变量。这样的变量和函数可以 然后在只允许编译时常量表达式的地方使用 (前提是给出了适当的函数参数)。一个常量表达式 对象声明中使用的说明符意味着 const。

    (引自http://en.cppreference.com/w/cpp/language/constexpr

    在实践中,您可以实现这样的功能:

    template<class T>
    constexpr T binomial_coefficient(const T n, const T k)
    {
        if ( 2 * k > n )
        {
            return binomial_coefficient(n, n - k);
        }
        else
        {
            return k ? binomial_coefficient(n, k - 1) * (n - k + 1) / k : 1;
        }
    }
    

    这可以在编译时进行评估。 举个例子,看看https://godbolt.org/g/b1MgFd,这个sn-p是由不同的编译器编译出来的,行

    constexpr auto value = binomial_coefficient(49, 43);
    

    成为

    mov     eax, 13983816
    

    【讨论】:

    • 我认为constexpr函数中的多个语句是C++14正式添加的。
    • @Phil1970 你是对的。在这种特殊情况下,通过使用带有另一个条件运算符的单个 return 而不是我使用的 if 可以轻松解决。
    • 感谢您的建议。我已经通过使用objdump 反汇编目标代码来验证它,并且确实计算的值在文件中。有趣的是,在没有事先分配给变量的情况下立即使用该值,例如std::cout &lt;&lt; binomial_coefficient(49, 43); 导致在编译时不计算值。
    • @Fabian 是的,还请注意,如果您将该变量声明为 const,clang 不会在编译时计算它,而 gcc 会。
    【解决方案3】:

    假设您使用的是现代 C++ 编译器,constexpr 的答案是最好的方法。

    但是,以下代码本质上是对您的代码的改进,可避免实例化过多的模板。实际上,据我所知,在您的示例中使用模板时,编译器将生成三元表达式中的所有模板,而不仅仅是一个是选定的一面。

    template <unsigned int n, unsigned int k>
    struct Binomialkoeffizient 
    {
        static const unsigned int k2 = (2 * k > n) ? n - k : k;
        static const unsigned int value = Binomialkoeffizient<n, k2 - 1>::value * (n - k2 + 1) / k2 ;
    };
    
    template <unsigned int n>
    struct Binomialkoeffizient<n, 0> {
        static const unsigned int value = 1;
    };
    

    通过定义k2,当2 * k &gt; n 为真时,我可以删除一个额外的级别。

    如果您使用的是 C++ 11 编译器,则可以将 const 替换为 constexpr。否则,使用未命名的enum 可能更可取,否则编译器可能仍会为每个实例化级别的value 成员保留内存。

    【讨论】:

    • 感谢 k2 的好主意。就在 n=k 的情况下,例如Binomialkoeffizient&lt;49, 49&gt;::value 您的程序不起作用,因为它会永久实例化新模板,直到达到限制。添加进一步的专业化template &lt;unsigned int n&gt; struct Binomialkoeffizient&lt;n, n&gt; { static const unsigned int value = 1; };helps.
    【解决方案4】:

    让我分享一下我在编译时对二项式系数的尝试。

    #include <iostream>
    
    template<unsigned N, unsigned K, typename = unsigned> // last arg for sfinae
    struct choose; // predeclaring template for use in choose_recursive
    
    template<unsigned N, unsigned K>
    struct choose_recursive // typical recursion for choose is done here
    {
        constexpr static unsigned value = choose<N - 1, K - 1>::value + choose<N - 1, K>::value;
    };
    
    struct choose_invalid
    {
        constexpr static unsigned value = 0;
    };
    
    template<unsigned N, unsigned K, typename>
    struct choose {
        constexpr static unsigned value = std::conditional_t<
            N >= K, choose_recursive<N, K>, choose_invalid
            >::value;
    };
    
    template<unsigned N>
    struct choose<N, 0> {
        constexpr static unsigned value = 1;
    };
    
    // sfinae to prevent this and previous specialization overlapping for (0, 0)
    template<unsigned K>
    struct choose <K, K, std::enable_if_t< 0 <  K, unsigned>> {
        constexpr static unsigned value = 1;
    };
    
    int main()
    {
        std::cout << choose<5, 2>::value << std::endl;
        std::cout << choose<5, 3>::value << std::endl;
        std::cout << choose<20, 10>::value << std::endl;
        std::cout << choose<0, 0>::value << std::endl;
        std::cout << choose<0, 1>::value << std::endl;
        std::cout << choose<49, 43>::value << std::endl;
    }
    

    此解决方案将适用于无符号类型的每个选择函数值,从而实际上解决了 OP 一直面临的溢出问题。一些 sfinae 用于只允许单项特化来匹配 choose,而 std::conditional_t 用于说明 N 的情况

    【讨论】:

      猜你喜欢
      • 2011-03-07
      • 2012-08-14
      • 2017-05-05
      • 2019-01-21
      • 1970-01-01
      • 1970-01-01
      • 2011-12-15
      • 2019-02-24
      • 1970-01-01
      相关资源
      最近更新 更多