0

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;
}
infinitezero
  • 1,610
  • 3
  • 14
  • 29
  • 1
    `std::size_t sum = 0;`, so clearly not `constexpr`. – Jarod42 Dec 08 '21 at 11:41
  • @Jarod42 fair enough. But the algorithm is deterministic thus it should be possible to know sum at compile time. How do I need to change the code? – infinitezero Dec 08 '21 at 11:42
  • Something along `constexpr std::array partition = make_partition(k); constexpr std::size_t sum = accumulate(partition.begin(), partition.end(), 0);` – Jarod42 Dec 08 '21 at 11:47
  • I might be missing something, but are any of those `constexpr if` or `constexpr_for` necessary? Regular `if` and `for` seem like they should work fine here. The point of `constexpr` functions is that you can write normal code that works at both compile-time and runtime. Same goes for the helper functions. – chris Dec 08 '21 at 11:48
  • @chris but I explicitely want code that can be evaluated at compile time. – infinitezero Dec 08 '21 at 11:51
  • @infinitezero What you have is code that can **only** be evaluated at compile time, while with standard `if` and `for` you would have code that could be executed both at compile and runtime. – Holt Dec 08 '21 at 12:03
  • @infinitezero, If you use the result of the function call in a way that requires it to be available at compile-time, the compiler has no choice but to evaluate it at compile-time. Since C++20, there's also `consteval` which is mostly the same, but _partially_ just a way to avoid accidentally evaluating it at runtime. – chris Dec 08 '21 at 12:05
  • @Jarod42 So what? Perfectly valid `size_t sum = 0; sum += a;` If it is not modified with something that can be passed at runtime. In the scope of its function it is `constexpr` https://godbolt.org/z/KTrvboWfG – Sergey Kolesnik Dec 08 '21 at 12:31
  • @SergeyKolesnik: Whereas `size_t sum = 0; sum += a;` might be used in `constexpr` function, `sum` is not `constexpr` though, so neither of `if constexpr (sum != 0)` nor `std::array` is correct. – Jarod42 Dec 08 '21 at 13:22
  • @Jarod42 it is true. I didn't see that usage, so it is good to clarify. There is a lot of code in this example, and the OP would not benefic from a "self-evident" fact in a comment. Comments should be helpful, not confusing. – Sergey Kolesnik Dec 08 '21 at 13:27

1 Answers1

4

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.

Holt
  • 36,600
  • 7
  • 92
  • 139