Search code examples
c++constexprc++23consteval

C++23 constexpr size limitations? Iterating through 0x4000 = ok, to 0x5000 = not a constant expression


The following code works and prints the intersection as expected. However, changing max_codepoint from 0x4000 to 0x5000 and it stops working with the error:

ConsoleApplication1.cpp(149, 30): [C2131] expression did not evaluate to a constant

I tried to change the 0x4000 to 0x5000 (or, more accurately, 0xffff to get the full UTF-16 range) and expected it to work. Yet, it doesn't.

#include <array>
#include <print>
#include <vector>

using ImWchar = unsigned short;

consteval std::vector<ImWchar> ConstGetGlyphRangesLatin()
{
    return {
        0x0020, 0x00FF, // Basic Latin + Latin Supplement
    };
}

consteval std::vector<ImWchar> ConstGetGlyphRangesGW()
{
    return {
        0x20, 0x7f,
        0xa1, 0xff,
        0x100, 0x180,
        0x0391, 0x0460,
        0x2010, 0x266b,
        0x3000, 0x3020,
        0x3041, 0x3100,
        0x3105, 0x312a,
        0x3131, 0x318f,
        0xac00, 0xd7a4,
        0x4e00, 0x9fa6,
        0xf900, 0xfa6b,
        0xff01, 0xffe7
    };
}

consteval bool in_range(const ImWchar c, const std::vector<ImWchar>& range)
{
    for (size_t i = 0; i < range.size() - 1; i += 2) {
        if (c >= range[i] && c <= range[i + 1]) return true;
    }
    return false;
};

consteval std::vector<ImWchar> find_glyph_range_intersection(const std::vector<ImWchar>& range1, const std::vector<ImWchar>& range2)
{
    if (range1.empty() || range2.empty()) return {};

    std::vector<ImWchar> intersection;
    wchar_t start = 0;
    bool in_intersection = false;

    constexpr auto max_codepoint = 0x4000;
    for (ImWchar c = 0; c <= max_codepoint; ++c) {
        const bool in_both = in_range(c, range1) && in_range(c, range2);

        if (in_both && !in_intersection) {
            start = c;
            in_intersection = true;
        }
        else if (!in_both && in_intersection) {
            intersection.push_back(start);
            intersection.push_back(c - 1);
            in_intersection = false;
        }
    }

    if (in_intersection) {
        intersection.push_back(start);
        intersection.push_back(max_codepoint);
    }

    intersection.push_back(0); // Null-terminate the range
    return intersection;
}

template <typename T, size_t N>
consteval std::array<T, N> vec_to_array(const std::vector<T>& vec)
{
    std::array<T, N> arr = {};
    std::copy(vec.begin(), vec.end(), arr.begin());
    return arr;
}

consteval auto get_intersection_array()
{
    const auto latin = ConstGetGlyphRangesLatin();
    const auto gw = ConstGetGlyphRangesGW();

    const auto intersect = find_glyph_range_intersection(latin, gw);
    return vec_to_array<ImWchar, 1'000>(intersect);
}

int main()
{
    constexpr std::array arr = get_intersection_array();
    for (auto c : arr) {
        if (c != 0)
            std::print("{}, ", c);
    }
}

Solution

  • An expression is not a constant expression if evaluating it would require more evaluation steps than some implementation-defined limit.

    That's to make sure that compilers aren't forced to work forever during compilation. Because of the halting problem it would otherwise be undecidable whether an expression is a constant expression.

    Compilers usually have an option with which this implementation-defined limit can be increased, trading for compilation time.

    As mentioned in a comment by @DubbleClick under this answer: The setting for MSVC seems to be /constexpr:steps.