how to check that const array members grow monotonically at compile time

111 Views Asked by At

assume we have const array:

const int g_Values[] = { ... };

how check that members grow monotonically at compile time, i.e. g_Values[i] < g_Values[i + 1]

in runtime this possible to check like this:

bool IsMonotonously()
{
    int i = _countof(g_Values);
    int m = MAXINT;
    do 
    {
        int v = g_Values[--i];
        if (v >= m) return false;
        m = v;
    } while (i);
    return true;
}

but how rewrite this with constexpr and if IsMonotonously() return false - generate compile time error.

2

There are 2 best solutions below

1
fabian On BEST ANSWER

This is impossible for an array that is just const. You need to make it constexpr to be able to use it in a constexpr context.

All you need to do in addition to this is to implement the function for checking the array as constexpr:

template<class T, size_t N>
constexpr bool IsStrictlyMonotonouslyIncreasing(T (&arr)[N])
{
    bool result = true;

    if (N > 1)
    {
        for (size_t i = 0; result && (i != N - 1); ++i)
        {
            result = (arr[i] < arr[i + 1]);
        }
    }

    return result;
}

const int g_Values[] = { 1, 2, 3, 4 };
static_assert(IsStrictlyMonotonouslyIncreasing(g_Values)); // compiler error g_Values is not usable in a constexpr context

constexpr int g_Values2[] = { 1, 2, 3, 4 };
static_assert(IsStrictlyMonotonouslyIncreasing(g_Values2)); // ok
2
A M On

Fantastic answer is given already. Just as an additional note:

Starting with C++20, several functions in the algorithm library have now also a constexpr implementation. This includes std::adjacent_find. And this also in the "ranges" version.

The solution to your problem is nearly given in the cpp reference here with using std::ranges::adjacent_find.

The example uses std::ranges::greater as the predicate which would allow for repeating values, resulting in none-strict ordering.

So, we need to use std::ranges::greater_equal. This will also deduce the parameter types of the function call operator from the arguments automatically, which makes live a little bit easier.

The code could look something like the below:

#include <iostream>
#include <cassert>
#include <algorithm>
#include <iterator>
#include <functional>
namespace rng = std::ranges;

template <typename T, size_t N>
constexpr bool increasingValues(T(&a)[N]) noexcept {
    return (rng::end(a) == rng::adjacent_find(a,  rng::greater_equal()));
}
constexpr int arr[] = { 10, 20, 20, 40, 50};

static_assert(increasingValues(arr), "Array values are not increasing");

int main() {
    std::cout << *(std::end(arr) - 1)<< '\n';
}

With Microsoft Visual Studio, the editor would already show the problem.

And the compiler would generate 2 messages with a clear hint.

Static assertion failed with "Array values are not increasing" 

Please see a screen shot below:

enter image description here




.

.

.

By the way, the accepted answer compiles like the below

enter image description here

And with fixing to constexpr, we can see:

enter image description here