在模板元编程中替换三元运算符

时间:2017-07-28 16:09:38

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

我在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;
};

这种实现的缺点是,它没有利用定理n在k> 1的情况下选择k = n选择n-k。 N / 2。因此可能发生不必要的算术溢出,例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

这似乎是由于在递归模板实例化过程中不评估三元运算符这一事实引起的。

使用?:有哪些可能的选择?

我对pre-C ++ 11解决方案和更新的解决方案感兴趣(可能std::enable_if有帮助,但我不太了解它。)

4 个答案:

答案 0 :(得分:1)

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

  

...(其中)声明可以评估该值   编译时的函数或变量。这些变量和功能可以   然后在只允许编译时常量表达式的地方使用   (只要给出了适当的函数参数)。一个constexpr   对象声明中使用的说明符意味着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此片段由不同编译器和行

编译的位置
constexpr auto value = binomial_coefficient(49, 43);

成为

mov     eax, 13983816

答案 1 :(得分:1)

假设您使用的是现代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 > n为真时,我可以删除一个额外的级别。

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

答案 2 :(得分:0)

经过一夜的睡眠,我认为我明白了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?)。

答案 3 :(得分:0)

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

#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用于仅允许selection <0,0>匹配的单个专业化,std :: conditional_t用于解决N