18

Currently you cannot use static_assert to verify parameters of a constexpr function, even if all calls to it are indeed constexpr. That makes sense because the compiler still has to create a non-constexpr instantiation of this function in case some other module will try to call it. Sadly, this is the case even if the function is static or in an anonymous namespace.

C++20 however, will introduce a new keyword consteval which is like constexpr but it doesn't allow calling a function in a non-constexpr way. In this case, the compiler can know for sure that the function parameters will always be known at compile time. Therefore, in theory it should be possible to validate them with static_assert.

The question is: Does the standard allow it?


Example:

#include <iostream>

consteval char operator""_bchar(const char text[], const size_t length)
{
    static_assert(length == 8, "Binary char has to have 8 digits!"); // <-- This is currently not possible.
    uint8_t byte = 0;
    for (size_t i = 0; i != length; ++i)
    {
        byte <<= 1;
        byte |= text[i] == '1' ? 0b00000001 : 0b00000000;
    }
    return byte;
}

int main()
{
    std::cout << "01000001"_bchar << std::endl;
    return 0;
}

I'm asking because I'm going to write some user-defined literals (more complicated than the example). I have an option to use compiler extensions to deal with the validation or wait a little for the compiler update and write fully standard-compliant code.

Boann
  • 47,128
  • 13
  • 114
  • 141
NO_NAME
  • 2,769
  • 1
  • 23
  • 56

1 Answers1

14

Will consteval allow to use static_assert on function arguments?

No. Function arguments have never been, and will continue to not be, usable as constant expressions.

There is a difference between something being constant evaluated and being usable as a constant-expression. consteval ensures that we're in a constant evaluation context, but it does not also cause everything to become constant-expressions.

In order to allow function arguments to be usable as constant expressions, you would need to make everything implicitly a template:

template <int> struct X { };

consteval auto foo(int i) {
    static_assert(i > 10); // in order to allow this...
    return X<i>{};         // ... you'd have to allow this too
}

And now foo(20) and foo(30) return different types. That's a template.


Important background reading for understanding why this is a fundamental and inherent limitation can be found in Andrew Sutton's Translation and evaluation: A mental model for compile-time metaprogramming:

Having a mental model of compile-time evaluation that physically separates it from the process of translation has been extremely helpful for me. In particular, it has helped me understand what is not possible (e.g., instantiating a template during evaluation). This helps prune the design space for otherwise large and complex language features. Hopefully, others will find this note helpful as well.


With static_assert specifically though, you can add a workaround just to cause a compilation failure. That's just adding anything at all that can't be used during constant evaluation. Like:

#define CONSTEVAL_STATIC_ASSERT(c, msg) do { if (!(c)) throw msg; } while(false)

as in:

consteval char operator""_bchar(const char text[], const size_t length)
{
    CONSTEVAL_STATIC_ASSERT(length == 8, "Binary char has to have 8 digits!");
    // ...
}
Barry
  • 267,863
  • 28
  • 545
  • 906
  • 1
    Your `CONSTEVAL_STATIC_ASSERT` does not fail at compile-time. It throws the exception at run-time. I use `g++ (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0`. – NO_NAME Jul 26 '19 at 20:58
  • 1
    Nevermind, it works if I try to assign it to a constexpr constant. – NO_NAME Jul 26 '19 at 21:05
  • 3
    `There is a difference between something being constant evaluated and being usable as a constant-expression.` What's the difference? – NO_NAME Jul 26 '19 at 21:09
  • The fact you would have to allow `X{};` doesn't mean you would have to also allow `return X{};`. I don't see a problem here. – NO_NAME Jul 26 '19 at 21:16
  • 1
    @NO_NAME It's already `X` that's the problem. And yes, allowing the former necessarily means allowing the latter. – Barry Jul 26 '19 at 21:36
  • 1
    The document about mental model really helped me to understand boundaries between translation and evaluation. I know why `X` will not be possible. Although, I still don't think it prevents `static_assert` from being used on `consteval` function parameters. It could be solved not by allowing them to be used as constant expressions but rather by loosening restrictions on `static_assert` so it can be used on constant evaluated values. This is possible even withing current language boundaries, as you've shown yourself. – NO_NAME Jul 26 '19 at 21:51
  • @Barry It would be a problem if it was in a normal function. Or even in a constexpr-function outside a block restricted to compile-time evaluation. – Deduplicator Jul 27 '19 at 12:07
  • @Deduplicator What would be a problem? – Barry Jul 27 '19 at 13:21
  • Taking advantage of the fact that the value of the argument is known at compile-time. – Deduplicator Jul 27 '19 at 13:30
  • @Deduplicator Sure, but the question is specifically about `consteval` where at least it would appear that those concerns don't apply. – Barry Jul 27 '19 at 15:55
  • @Barry As I said. Consider adding that it is an arbitrary limitation to the answer. – Deduplicator Jul 27 '19 at 23:16
  • @Deduplicator What is an arbitrary limitation? I do not think anything here is arbitrary. – Barry Jul 28 '19 at 11:17
  • That an immediate function cannot use its arguments as non-type template arguments is an arbitrary implementation, as it is restricted to compile-time anyway. – Deduplicator Jul 28 '19 at 11:22
  • @Deduplicator It's not arbitrary at all. – Barry Jul 28 '19 at 13:52
  • @Deduplicator It's completely consistent with the way everything else in the language works and it's completely consistent with the model for how constant evaluation works. Read Andrew's short paper that I linked. – Barry Jul 28 '19 at 14:31
  • Andrew separates code into two buckets: Compiletime (translation) and runtime (evaluation). Normal functions run at runtime, constexpr both, and consteval only at compiletime. So, using runtime limitations for consteval is very restricting and arbitrary. – Deduplicator Jul 28 '19 at 15:14
  • 3
    Hmm, I suppose that consteval functions are already required to have their code be present on call time. So what's the problem of implicitly making them templates? – Johannes Schaub - litb Aug 04 '19 at 12:16
  • Nice answer, but I think it is not entirely correct. In your example you claim "... you'd have to allow this too" but that is not true. It would be perfectly fine(but inconsistent, and maybe hard to teach) that static_assert can see values of arguments, but that those arguments can not be used as template arguments. – NoSenseEtAl Nov 05 '20 at 14:06
  • What's the problem with `foo()` being `template X foo();` with some new deduction guide for functions `foo(int i) -> foo()`? Deduction guides for functions would be nice anyway. – Goswin von Brederlow Jun 04 '22 at 03:00