Search code examples
c++c++17constexprtemplate-meta-programmingif-constexpr

Generate constexpr array (error: the value of 'sum' is not usable in a constant expression)


The Problem

I need to generate all possible partitions of an integer m into the sum of j elements a_k, where each a_k can be -1, 0, or 1. This is a deterministic algorithm and as such it should be able to implement it at compile time. I would like to return a std::array with all possible combinations as constexpr.

My Algorithm

Plain and simple, there are 3^j combinations in total. So we loop over all of them and check if the sum is m. The total number of valid combinations will be

enter image description here

\sum_{k=m}^{\lfloor (m+j)/2\rfloor}\binom{j}{k}\binom{j-k}{k-m}

Thus we can calculate the size of the array (which is j times the number above) and simply queue in all the number combinations we obtain by brute force.

My Code

Check it on Godbolt

I obtain the error

error: the value of 'sum' is not usable in a constant expression 88 | if constexpr( sum == m )

I fail to see however, how sum is not known at compile time.

How can I fix this?

#include <array>
#include <iostream>
#include <utility>

/** constexpr for loop **/
template <auto Start, auto End, auto Inc, class F>
constexpr void constexpr_for(F&& f)
{
    if constexpr (Start < End)
    {
        f(std::integral_constant<decltype(Start), Start>());
        constexpr_for<Start + Inc, End, Inc>(f);
    }
}

/** constexpr binomials **/

template<std::size_t n, std::size_t k>
struct Binomial
{
  constexpr static std::size_t value =  (Binomial<n-1,k-1>::value + Binomial<n-1,k>::value);
};

template<>
struct Binomial<0,0>
{
  constexpr static std::size_t value = 1;
};

template<std::size_t n>
struct Binomial<n,0>
{
  constexpr static std::size_t value = 1;
};

template<std::size_t n>
struct Binomial<n,n>
{
  constexpr static std::size_t value = 1;
};

template<std::size_t n, std::size_t k>
constexpr std::size_t binomial()
{
  return Binomial<n,k>::value;
}

/** formula from the picture **/

template<std::size_t j, std::size_t m>
constexpr std::size_t n()
{

    std::size_t result = 0;
    constexpr_for<m, (j+m)/2+1, 1>([&result](auto k){
        result += binomial<j, k>() * binomial<j-k, k-m>();
        });
    return result;
}

/** constexpr power function **/

template<std::size_t i, std::size_t j>
struct pow_t
{
    constexpr static std::size_t value = i * pow_t<i, j-1>::value;
};

template<std::size_t i>
struct pow_t<i, 0>
{
    constexpr static std::size_t value = 1;
};

template<std::size_t i, std::size_t j>
constexpr std::size_t pow()
{
    return pow_t<i, j>::value;
}

/** actual function in question **/


template<std::size_t j, std::size_t m>
constexpr std::array<int, j*n<j,m>()> integer_compositions()
{
    std::array<int, j*n<j,m>()> result;
    std::size_t i = 0;
    constexpr_for<0, pow<3, j>(), 1>([&](auto k)
    {
        std::array<std::size_t, j> partition;
        std::size_t sum = 0;
        constexpr_for<0, j, 1>([&](auto l)
        {
            partition[l] = -((k/static_cast<std::size_t>(pow<3,l>()))%3-1);
            sum += partition[l];
        });
        if constexpr( sum == m ) // line 88
        {
            constexpr_for<0, j, 1>([&](auto l)
            {
                result[j*i + l] = partition[l];
            });
            ++i;
        }
    });
    return result;
}

int main()
{
    constexpr auto list = integer_compositions<3, 1>();
    return EXIT_SUCCESS;
}

Solution

  • You're confusing the purpose of constexpr function. A constexpr function can be executed both at runtime and as part of constant expression, that depends on how you use the function (and probably if the compiler wants to optimize things at compile time).

    You don't need all these templated functions, since the whole purpose of constexpr functions is to avoid them for clarity, e.g., your binomial function can simply be:

    constexpr std::size_t binomial(std::size_t n, std::size_t k) {
        if (k == 0 || n == k) {
            return 1;
        }
        else {
            return binomial(n - 1, k - 1) + binomial(n - 1, k);
        }
    }
    

    And you can then do:

    // binomial(3, 2) is evaluated at compile-time
    std::array<int, binomial(3, 2)> x;
    static_assert(x.size() == 3);
    

    This can be done for all of your functions, except the last one (integer_compositions) because the return type depends on the parameter, so you need a template here.

    There are other issues in your code, for instance, you need to initialize your std::array in constexpr function, so std::array<..., ...> result{}(note the {} here).


    Below is a working version of your code that does not use all of these templates (this requires C++20, but only because operator== for std::array is only constexpr since C++20):

    #include <array>
    #include <iostream>
    
    /** constexpr binomials **/
    
    constexpr std::size_t binomial(std::size_t n, std::size_t k) {
        if (k == 0 || n == k) {
            return 1;
        }
        else {
            return binomial(n - 1, k - 1) + binomial(n - 1, k);
        }
    }
    
    /** formula from the picture **/
    
    constexpr std::size_t n(std::size_t j, std::size_t m)
    {
    
        std::size_t result = 0;
        for (std::size_t k = m; k <= (j + m) / 2; ++k) {
            result += binomial(j, k) * binomial(j - k, k - m);
        }
        return result;
    }
    
    /** constexpr power function **/
    
    constexpr std::size_t pow(std::size_t a, std::size_t n) {
        std::size_t r = 1;
        while (n--) {
            r *= a;
        }
        return r;
    }
    
    /** actual function in question **/
    
    
    template<std::size_t j, std::size_t m>
    constexpr std::array<int, j*n(j, m)> integer_compositions()
    {
        std::array<int, j*n(j, m)> result{};
        std::size_t i = 0;
        for (std::size_t k = 0; k < ::pow(3, j); ++k) {
            std::array<std::size_t, j> partition{};
            std::size_t sum = 0;
            for (std::size_t l = 0; l < j; ++l) {
                partition[l] = -((k/static_cast<std::size_t>(::pow(3, l)))%3-1);
                sum += partition[l];
            }
            if (sum == m) // line 88 
            {
                for (std::size_t l = 0; l < j; ++l) {
                    result[j*i + l] = partition[l];
                }
                ++i;
            }
        }
        return result;
    }
    
    int main()
    {
        static_assert(integer_compositions<3, 1>() == std::array<int, 18>{});
    }
    

    Note: The static assertion obviously fails because I have no clue what the 18 values are.