Sopel
Sopel

Reputation: 1219

constexpr function with a conditionally constexpr callee

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

Answers (2)

bolov
bolov

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

perivesta
perivesta

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

Related Questions