4

The problem

Using static_assert to generate compile-time error is not always easy because it requires a constant expression as first argument. I found, on StackOverflow, several example where throw was used instead of static_assert:

static_assert(<constante evaluated condition>,"message");
if (<condition>) {throw "message";}

My question is, is it legal to do that inside constexpr or consteval function.

For instance, if I want to check at compile time if some integer is positive (pretty dumb...) I may write:

CONSTSPEC void checkpos(int x) {
    if (x < 0) {
        // is this ill-formed, no diagnostic required (when called with a
        // negative argument?)
        throw "argument must be positive";
    }
}

LIVE

Where CONSTSPEC can be constexpr or consteval.

With constexpr

By looking at https://timsong-cpp.github.io/cppwp/n4861/dcl.constexpr#6 and also this answer it seems that calling checkpos with a strictly negative argument is ill-formed but no diagnostic is required, letting a compiler do whatever he wants, which would be useless for compile-time error detection.

With consteval

I can't find a word in the standard. Should I understand that then any call is well-formed and passing a strictly negative argument will result in a mandatory compile-time error?

Question

Is throw usable inside a constexpr function (I don't think so) or inside a consteval function (maybe) to generate a compile-time error?

4
  • 1
    I wouldn't do stuff like this. Throwing exceptions during constant evaluation has already been accepted for C++26 open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3068r6.html I think, so you don't want to rely on throw being invalid forever. Commented Jun 6 at 11:54
  • You only have IFNDR for constexpr function which unconditionally cannot provide constant expression. call with negative value just don't provide constant expression (and so error-out when use in expression expecting a constant expression. Commented Jun 6 at 11:59
  • @Jarod42 Ah, I didn't get it right. Thus in my example, which has void as returned type, the section I am referencing simply does not apply. So no IFNDR, I just don't get now what is the mandate behavior of a "compile-time" throw (pre-C++26 wrt to cigien comment) Commented Jun 6 at 12:10
  • 1
    A "compile-time" throw makes(made) the expression not a constant expression; with the paper, it is the fact to not catch that exception which makes not a constant expression. Commented Jun 6 at 12:16

1 Answer 1

3

My question is, is it legal to [throw exceptions] inside constexpr or consteval function.

Before C++26, this would make the surrounding constant expression not a constant expression, which would usually have the same effect as a failed static_assert. If you don't intend to catch the exception anyway, you can just throw, goto, or do something else that's not allowed in constant expressions. People just use throw "message" by convention, but x: goto x; // message would have the same effect.

After P3068R6: Allowing exception throwing in constant-evaluation in C++26, you can also catch exceptions thrown at compile time. Considering that, it may be best to write:

throw std::invalid_argument("argument must be positive");

... which would work exactly as expected after C++26 and have a useful exception type, and behave like an "uncatchable exception" before that.

By looking at https://timsong-cpp.github.io/cppwp/n4861/dcl.constexpr#6 and also this answer it seems that calling checkpos with a strictly negative argument is ill-formed but no diagnostic is required, letting a compiler do whatever he wants, which would be useless for compile-time error detection.

No, you misunderstand that. The program would be ill-formed if you wrote a function like:

constexpr void f() { throw; }

The condition is that

no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression

... but in the case of checkpos, you could call checkpos(0), and that would be a constant expression. Whether you always call it with negative arguments or not doesn't matter. You could call it with some arguments that would make it a constant expression. For consteval, the same rules would apply since both constexpr and consteval make a function a "constexpr function".

Note that these restrictions no longer exist in C++23 after P2448R2: Relaxing some constexpr restrictions The declaration of f() above would be okay.

Sign up to request clarification or add additional context in comments.

2 Comments

I indeed misunderstood the restriction, thanks for the clarification. It could be added, for the use-case of generating compile-time error, that constexpr function are not good candidate because they require compile-time arguments AND a constant evaluated context to be guaranteed to be evaluated at compile-time (in my linked example, the error was triggered at runtime). Besides, with consteval function (or constexpr in the right context), any non-const-whatever function could be used instead of throw in order to generate a compile-time error. Example in next comment.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.