Reputation: 1219
example code:
#include <cstdint>
//#define USE_CONSTEXPR_INTRINSICS
#if defined (USE_CONSTEXPR_INTRINSICS)
namespace intrin
{
[[nodiscard]] constexpr int lsb(std::uint64_t value)
{
int r = 0;
while (value <<= 1) ++r;
return 63 - r;
}
}
#else
namespace intrin
{
[[nodiscard]] inline int lsb(std::uint64_t value)
{
return __builtin_ctzll(value);
}
}
#endif
constexpr int f(std::uint64_t v)
{
return intrin::lsb(v);
}
#if defined (USE_CONSTEXPR_INTRINSICS)
static_assert(f(13) == 3);
#endif
int main() {}
there is an 'intrinsic' function lsb
that I can conditionally compile to be constexpr - to allow static tests with static_assert (which are also conditionally compiled, same as the constexpr intrinsics).
The problem comes from the fact that f
is marked constexpr - which makes it ill-formed when lsb
is not constexpr.
Currently I 'solve' this by having
#if defined (USE_CONSTEXPR_INTRINSICS)
#define INTRIN_CONSTEXPR constexpr
#else
#define INTRIN_CONSTEXPR inline
#endif
and then declaring f
as
INTRIN_CONSTEXPR int f(std::uint64_t v)
but for me this solution is not ideal, because there are many functions that use lsb
, sometimes very indirectly, and it's becoming both hard to track (no diagnostics required, so sometimes it differs by compiler) and out of place. What I would prefer is having the check only done when needed - like around the static_assert - and the compiler being silent otherwise.
Is there currently any way to make this more automatic? Is there any hope that it will be easier in the future?
Upvotes: 2
Views: 81
Reputation: 75696
C++20 makes this trivial with std::is_constant_evaluated
[[nodiscard]] constexpr int lsb(std::uint64_t value)
{
if (std::is_constant_evaluated())
{
int r = 0;
while (value <<= 1) ++r;
return 63 - r;
}
else
{
return __builtin_ctzll(value);
}
}
In constexpr context:
static_assert(lsb(1) == 0); // OK
And in runtime context:
auto test(int n)
{
return lsb(n);
}
compiles to:
test(int):
movsx rax, edi
rep bsf rax, rax
ret
Please note that it is not if constexpr(std::is_constant_evaluated())
. That would always return true
.
For anyone looking at this, the alg is incomplete for brevity, lsb(0)
should return 64
.
Upvotes: 2
Reputation: 4021
You should be able to make both versions constexpr like this:
#include <cstdint>
#define USE_CONSTEXPR_INTRINSICS
namespace intrin
{
[[nodiscard]] constexpr int lsb(std::uint64_t value)
{
# if defined (USE_CONSTEXPR_INTRINSICS)
int r = 0;
while (value <<= 1) ++r;
return 63 - r;
# else
return __builtin_ctzll(value);
# endif
}
}
constexpr int f(std::uint64_t v)
{
return intrin::lsb(v);
}
#if defined (USE_CONSTEXPR_INTRINSICS)
static_assert(f(0) == 63);
#endif
int main(){}
Compiles fine with g++ and clang++ with -std=c++14
or -std=c++17
Upvotes: 0