2

I want to simplify a code by using a constexpr function instead of multi constexpr if branches.

This is the code with the old code commented the old code compiles with msvc (vs 2017 with c++17) and clang (android ndk r20), but it fails to compile with clang 8 in for windows x64 in visual studio !

and the new code neither compiles with msvc nor clang

template <class T>
constexpr bool IsValueNegative(T value) // this function should be evaluated at runtime but it it isn't !
{
    if constexpr (std::is_integral_v<T>) // SOCKET = ULONG_PTR and linux handles are int
    {
        return value < 0;
    }
    else // HANDLE = void * and most other handles are pointers
    {
        return (intptr_t)value < 0;
    }
}

template <class T, const T null_value, bool no_negative = true>
class HandleWrapper
{
public:
    HandleWrapper() : handle(null_value)
    {}
    HandleWrapper(T h) : handle(h)
    {
        if constexpr (no_negative) // to convert INVALID_HANDLE_VALUE to nullptr
        {
            if constexpr (!IsValueNegative(null_value)) // SOCKET invalid handle is -1
            {
                if (IsValueNegative(handle)) // 
                    handle = null_value;
            }
            /*
            if constexpr (std::is_integral_v<T>)
            {
                if constexpr (null_value >= 0)
                {
                    if (handle < 0)
                        handle = null_value;
                }
            }
            else
            {
                if constexpr ((intptr_t)null_value >= 0) // clang 8 can't compile this , don't know why
                {
                    if ((intptr_t)handle < 0)
                        handle = null_value;
                }
            }
            */
        }
    }
private:
    T handle;
};

template <class T, const T null_value, bool no_negative, auto Deleter>
struct HandleHelper
{
    using pointer = HandleWrapper<T, null_value, no_negative>;
    void operator()(pointer p)
    {
        if constexpr (!no_negative)
        {
            if (!IsValueNegative(null_value) && IsValueNegative(T(p))) // pseudo handle from GetCurrentProcess or GetCurrentThread
                return;
        }
        /*
        if constexpr (!no_negative && )
        {
            if ((uintptr_t)T(p) <= 0)
            {
                std::cout << "[*] this is a pseudo handle\n";
                return;
            }
        }
        */
        Deleter(T(p));
    }
};

using Handle = std::unique_ptr<HandleWrapper<HANDLE, nullptr, true>, HandleHelper<HANDLE, nullptr, true, CloseHandle>>;
using ProcessHandle = std::unique_ptr<HandleWrapper<HANDLE, nullptr, false>, HandleHelper<HANDLE, nullptr, false, CloseHandle>>;
using ThreadHandle = ProcessHandle;

The new code fails to compile in this line :

if constexpr (!IsValueNegative(null_value)) // SOCKET invalid handle is -1

the error code from msvc is :

Error C2131 expression did not evaluate to a constant

and from clang 8 :

constexpr if condition is not a constant expression

however all the null_value is known at compile time

Robur_131
  • 374
  • 5
  • 15
dev65
  • 1,440
  • 10
  • 25
  • 2
    Please provide a [mcve]. – Barry Aug 22 '19 at 13:52
  • what should I post else to meet this minimal reproducible example – dev65 Aug 22 '19 at 13:56
  • 4
    There's a lot of stuff here that isn't strictly related to the problem, it can be removed (at the very least, all the commented out code?). And there are types and names that aren't defined - that aren't really relevant to the problem either and can be replaced by simpler things. – Barry Aug 22 '19 at 13:58

1 Answers1

4

Clang gives this error when you run it:

note: cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression
        return (intptr_t)value < 0;

Casting a pointer to an integer type is not a constant expression (because it is a reinterpret_cast), so you can't use if constexpr.

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • so why msvc and clang from ndk r20 allow it ? – dev65 Aug 22 '19 at 13:58
  • 3
    @dev65: They *don't*; that is why you're getting an error. It's just that their errors aren't as clear. If you call `IsValueNegative` with a `T` that isn't integral, one whose conversion is a `reinterpret_cast`, the function isn't `constexpr`. You're still allowed to write it and call it, but you cannot call it in a constant expression context. – Nicol Bolas Aug 22 '19 at 14:00
  • @dev65 It shouldn't allow it. I have no idea why they do, as reinterpret_cast has never been allowed in a constant expression. – Artyer Aug 22 '19 at 14:01
  • @NicolBolas but if I don't use the function and cast directly as in the comments the code compiles – dev65 Aug 22 '19 at 14:02
  • @dev65 Another reason is that it simply isn't called (In android, you might use `int` handles instead of `void*`, so it would compile) – Artyer Aug 22 '19 at 14:05
  • @Artyer no I'm using it with sqlite3 opaque pointers : 'using SqlHandle = std::unique_ptr, HandleHelper >; using SqlStatementHandle = std::unique_ptr, HandleHelper>;' – dev65 Aug 22 '19 at 14:07
  • and I found that c style cast like what I'm doing above is allowed in constexpr expressions – dev65 Aug 22 '19 at 14:12
  • @dev65 c-style casts are allowed if they are the same as a static_assert (e.g., `(int64_t) 0`). They are not allowed if they are a reinterpret_cast (e.g. `(char*) &var;`) – Artyer Aug 22 '19 at 14:15
  • @Artyer so it looks like msvc doesn't follow the standard here . and for clang from ndk : it didn't hit this branch because no_negative was false so no checks was performed . https://social.msdn.microsoft.com/Forums/vstudio/en-US/8937807a-5633-490c-9c94-b42dcd68a098/constexpr-allows-reinterpretcast-in-function-body?forum=vclanguage – dev65 Aug 22 '19 at 14:41