c++language-lawyerc++20

Is there a way to assert something about an argument to a consteval function, with a meaningful error message on failure


I want to have (in C++20) a consteval constructor that takes a string_view as an argument, and assert something about the properties of this argument at compile-time. For example:

#include <cstdlib>
#include <string_view>

constexpr bool IsValid(std::string_view foo) {
    return foo.length() > 2;
}

struct Wrapper {
    consteval Wrapper(const char* name) : name_(name) {
        // Doesn't work because name is not an integral constant expression
        static_assert(IsValid(name), "Must be valid");
    }

    std::string_view name_;
};

int main() {
    // Should compile fine
    Wrapper ok { "ABC" };
    // Should fail to compile
    Wrapper bad { "A" };

    return 0;
}

Based on this question and this other question, I figured I could get most of the way by replacing the static_assert with an if block that contains something that's doesn't compile. For example, the constructor can become:

consteval Wrapper(const char* name) : name_(name) {
    if (!IsValid(name)) {
        std::abort();
    }
}

Which behaves as desired and fails on invalid strings.

That said, the error message is pretty bad at indicating what the error is for users of this API. In this case, clang's error is:

<source>:23:13: error: call to consteval function 'Wrapper::Wrapper' is not a constant expression
   23 |     Wrapper bad { "A" };
      |             ^
<source>:11:13: note: non-constexpr function 'abort' cannot be used in a constant expression
   11 |             std::abort();
      |             ^
<source>:23:13: note: in call to 'Wrapper(&"A"[0])'
   23 |     Wrapper bad { "A" };

I could instead define an empty, not-constant-evaluatable function with a const char* argument to at least get a message in the error. Something like:

void bad_name(const char*) {}

consteval Wrapper(const char* name) : name_(name) {
    if (!IsValid(name)) {
        bad_name("name passed to Wrapper is invalid");
    }
}

Which fails with:

<source>:25:13: error: call to consteval function 'Wrapper::Wrapper' is not a constant expression
   25 |     Wrapper bad { "A" };
      |             ^
<source>:13:13: note: non-constexpr function 'bad_name' cannot be used in a constant expression
   13 |             bad_name("The name supplied to Wrapper is invalid");
      |             ^
<source>:25:13: note: in call to 'Wrapper(&"A"[0])'
   25 |     Wrapper bad { "A" };

Am I missing an obvious assert()-type facility in the C++ language or standard library that could be used in this context? If not, is there anything in the language itself that would make standardizing something like this impractical?


Solution

  • Calling a non-constexpr function in a way that gets across the message you want printed (such as calling it with an argument string, or naming the function itself something like The_name_supplied_to_Wrapper_is_invalid), is the usual way to do it. You can also throw the desired string (i.e. with the throw keyword), which causes an immediate constant evaluation failure. (In C++26, throwing an exception during constant evaluation will be allowed, but the constant evaluation will still fail when the exception propagates out of it, and compilers will probably print the string thrown in that case.) There is also a facility proposed by P2758 that will allow you to directly control the diagnostic message by calling std::constexpr_error_str. (This proposal will probably make it into C++26, but I don't want to jinx it.)