1

Imagine this simple constexpr function:

// Whatever, the exact values don't matter for this example
constexpr float items[100] = { 1.23f, 4.56f };
constexpr int length = 12;

constexpr float getItem(int index)
{
    if (index < 0 || index >= length)
    {
        // ArrayIndexOutOfRangeException has a constructor that takes a const char* and one that takes a std::string.
        throw ArrayIndexOutOfRangeException("You did a bad.");
    }
    return items[index];
}

You can use it like this:

int main()
{
    constexpr float f1 = getItem( 0); std::cout << f1 << std::endl; // Works fine
    constexpr float f2 = getItem( 1); std::cout << f2 << std::endl; // Works fine
    constexpr float f3 = getItem(-1); std::cout << f3 << std::endl; // Does not compile: "constexpr variable 'f3' must be initialized by a constant expression", "subexpression not valid in a constant expression"
    constexpr float f4 = getItem(20); std::cout << f4 << std::endl; // Does not compile: "constexpr variable 'f4' must be initialized by a constant expression", "subexpression not valid in a constant expression"
    return 0;
}

Great! BUTT WEIGHT!

    volatile int i;
    i = 123; // As a placeholder for something like this: std::cin >> i;
    float f5 = getItem(i);
    std::cout << f5 << std::endl;

This throws at runtime with "terminate called after throwing an instance of 'ArrayIndexOutOfRangeException'" and "what(): You did a bad."
Ok, that's not very helpful and I want to create a better error message:

constexpr float getItem(int index)
{
    if (index < 0 || index >= length)
    {
        std::stringstream stream;
        stream << "You did a bad. getItem was called with an invalid index (" << index << "), but it should have been non-negative and less than the total number of items (" << length << ").";
        throw ArrayIndexOutOfRangeException(stream.str());
    }
    return items[index];
}

But that's not allowed: "variable of non-literal type 'std::stringstream' (aka 'basic_stringstream') cannot be defined in a constexpr function".
I'd be OK with having a simpler error message for the compile-time version and only do the complicated string manipulation in the run-time version.

So... How?

Please note that this is C++17 (some GCC flavor), which might not have some constexpr-related features that C++20 would have.

Other code requires that the function stays constexpr.
I would like to avoid duplicating the function, if at all possible.

Niko O
  • 406
  • 3
  • 15

1 Answers1

1

You might move the error message creation in another function:

std::string get_error_message(int index, int length = 10)
{
    std::stringstream stream;
    stream << "You did a bad. getItem was called with an invalid index ("
           << index
           << "), but it should have been non-negative "
           << "and less than the total number of items ("
           << length << ").";
    return stream.str();
}

constexpr float getItem(int index)
{
    constexpr int length = 10;
    constexpr std::array<float, length> items{};

    if (index < 0 || index >= length)
    {
        throw std::runtime_error(get_error_message(index, length));
    }
    return items[index];
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • It's incredibly stupid that this works, but it does work! I can even sneak the string manipulation code back into `getItem` if I use a lambda function: `throw ArrayIndexOutOfRangeException(([&]() { /* std::stringstream stream; Do string stuff; return stream.str(); */ })());` but a less silly way of not having to pass a bunch of arguments (and a ton of template parameters, in my real-world case) around would be nice. – Niko O Apr 19 '22 at 13:15
  • From [cppreference](https://en.cppreference.com/w/cpp/language/constexpr), the restriction about *"a definition of a variable of non-literal type"* should be removed in C++23. – Jarod42 Apr 19 '22 at 13:25
  • Cool. So in ~30 years, when it's finally in the official spec, when GCC has implemented it, when buildroot has updated their GCC version, and when our hardware supplier updates our build environment, I can finally jump through one less hoop when writing error messages. On a different note: Is that undefined behavior? The more I read up on this, the more I fear it only works by accident. See https://stackoverflow.com/a/22177425/4242201 https://stackoverflow.com/a/21735324/4242201 – Niko O Apr 19 '22 at 13:40
  • No UB here. (UB happens for example if `index < 0 || index >= length` is always `true`, so no path leads to constant expression) there is a valid path here. And we don't violate any requirements. – Jarod42 Apr 19 '22 at 14:23
  • Ok, then. I'll wait a few days to see whether someone can come up with a neather solution (and whether any unexpected problems come up). Otherwise I'll mark your answer as accepted. – Niko O Apr 19 '22 at 14:32