1

I'm trying to implement std::lcm and I need to static_assert that the arguments or result are within bounds as required by the standard:

The behavior is undefined if |m|, |n|, or the least common multiple of |m| and |n| is not representable as a value of type std::common_type_t<M, N>.

Simplified the problem is this: Say I have the following code:

constexpr void foo(int x) noexcept {
    if consteval {
        static_assert(x == 0, "x must be 0");
    }
}

Compiling a c++23 gives:

<source>: In function 'costexpr void foo(int) noexcept':
<source>:3:25: error: non-constant condition for static assertion
    3 |         static_assert(x == 0, "x must be 0");
      |                       ~~^~~~
<source>:3:25: error: 'x' is not a constant expression

How do I static_assert that x == 0? Or something equivalent that shows the message on error.

Note: This is not a duplicate of Will consteval allow using static_assert on function arguments? as noexcept can't throw.

This is not a duplicate of static_assert compile-time argument check with C++20 concept because the use case (implementing std::lcm) does not allow template arguments.

This is not a duplicate of How to tell static_assert that constexpr function arguments are const?, same reason.

Sebastian
  • 1,834
  • 2
  • 10
  • 22
Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42
  • Perhaps use `if (...) throw (...)` anyway [demo](https://godbolt.org/z/9TTW38MhT) – n. m. could be an AI Jun 04 '22 at 05:48
  • @n.1.8e9-where's-my-sharem. That gives a hard to read error message: https://godbolt.org/z/8sbWba375 When I did this in `lcm` gcc always warned about the throw because noexcept means throw terminates. – Goswin von Brederlow Jun 04 '22 at 05:52
  • You can't `static_assert` on the runtime value of a variable. The standard doesn't require an error, it says that the behaviour is undefined, therefore you can do whatever you like when an out of range argument is passed. You can throw an exception, return the wrong answer, call `std::terminate` or anything else you like – Alan Birtles Jun 04 '22 at 07:16
  • @AlanBirtles Except a constexpr must not have UB. So no, you can't do just anything. – Goswin von Brederlow Jun 04 '22 at 07:22
  • E.g libc++ [aborts](https://github.com/llvm/llvm-project/blob/36c7d79dc4c114728b5f003bf48cd7a41bf932a4/libcxx/include/__numeric/gcd_lcm.h#L75) if the result is out of range – Alan Birtles Jun 04 '22 at 07:22
  • @AlanBirtles How is `_LIBCPP_ASSERT` defined? Anything that could be ported to gcc? – Goswin von Brederlow Jun 04 '22 at 07:25
  • 1
    `static_assert` make sense just at compile time evaluation, so are you asking for a way to ignore it when `foo()` is called in a non-constexpr context (runtime)? – MatG Jun 04 '22 at 07:32
  • @MatG No. `if consteval` already does that. The error happens when it's a const evaluation. – Goswin von Brederlow Jun 04 '22 at 07:39
  • "hard to read error message" In my day we had to wade through pages and pages of template instantiation errors (in the snow uphill both ways). The message there is quite passable. "gcc always warned about the throw" Then perhaps you should show your real code and ask a question about that. – n. m. could be an AI Jun 04 '22 at 07:48
  • Think it just does `std::abort` https://releases.llvm.org/8.0.0/projects/libcxx/docs/DesignDocs/DebugMode.html – Alan Birtles Jun 04 '22 at 07:48
  • Libstdc++ [just returns the wrong answer](https://github.com/gcc-mirror/gcc/blob/58b67140de7685de25b2f5775b5735f9c491b058/libstdc%2B%2B-v3/include/std/numeric#L147) – Alan Birtles Jun 04 '22 at 07:50
  • @AlanBirtles That's the bit I want to fix. – Goswin von Brederlow Jun 04 '22 at 08:08

1 Answers1

0

Best I can manage is this:

#include <cstdlib>

constexpr int foo(int x) noexcept {
    if consteval {
        if (x !=0) {
            std::abort(); // "x must be 0"
        }
    }
    return x;
}

constinit int x = foo(1);
constinit int y = foo(0);

This generates the lengthy error output:

<source>:12:15: error: variable does not have a constant initializer
constinit int x = foo(1);
              ^   ~~~~~~
<source>:12:1: note: required by 'constinit' specifier here
constinit int x = foo(1);
^~~~~~~~~
<source>:6:13: note: non-constexpr function 'abort' cannot be used in a constant expression
            std::abort(); // "x must be 0"
            ^
<source>:12:19: note: in call to 'foo(1)'
constinit int x = foo(1);
                  ^
/usr/include/stdlib.h:591:13: note: declared here
extern void abort (void) __THROW __attribute__ ((__noreturn__));
            ^

The "x must be 0" is pretty hidden, which I'm not happy about.

Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42