How to implement std::lcm to be standard conform?

195 Views Asked by At

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.

1

There are 1 best solutions below

2
Goswin von Brederlow On

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.