How do we handle errors in the input of a User Defined Literal?

Say I want to have an integral percent defined like so:

constexpr int operator""_percent (char const * literal)
{
    int percent(0);
    for(; *literal != '\0'; ++literal)
    {
        if(*literal >= '0' && *literal <= '9')
        {
            percent *= 10;
            percent += *literal - '0';
            if(percent > 100)
            {
                ...what do we do here?...
            }
        }
        else
        {
            ...what do we do here?...
        }
    }
    return percent;
}

I was thinking of throwing, but I remember that a constexpr and throw do not match well (or is that only in C++14 and older?). What is the convention in that situation? What is the correct way to report an error in a User Defined Literal operator?

Note: I'm currently on C++17 but plans to soon switch to C++20.


Solution 1:

I don't really know what common practice for such situations is, but I will give some ideas below.


With C++20 you can make the literal operator consteval instead of constexpr.

On error you can then throw an exception. The throw expression itself is allowed in a constexpr/consteval function, but actually throwing makes it not a constant subexpression. Since consteval function invocations must be constant expressions, the program will then be ill-formed on error and the compiler must diagnose it.


For C++17 you can still throw an exception, but consteval is not available.

For valid literals this is not a problem. As long as the throw expression wouldn't be evaluated, the call to the literal operator can still be a constant subexpression.

If however the literal is invalid, you will generally not get a compile-time diagnostic if it isn't used in a context requiring a constant expression.

Instead you will get an exception thrown during program execution when the literal is evaluated. This might be undesirable, for example if the literal is supposed to be used in a noexcept function.

Alternatively you could also use assert instead of throwing exceptions. Since C++17 it is guaranteed to be a constant subexpression if the argument evaluates to true.

If the assertion fails, you will not get a compile-time error either, instead the program will abort when the literal is evaluated.

Using the NDEBUG macro the assertion can be removed in release builds. This doesn't affect the exception specification, but you will still need to decide on how to handle errors in the release build which weren't caught in debug build testing. This might not be safe enough for your use.

I don't think there is any C++17 way of forcing a compile-time error in general.


As I mentioned in the comments to the question, I have some doubts that my suggestions are correct/good. So read them as well, please.