Home > Enterprise >  constexpr evaluation in combination failed
constexpr evaluation in combination failed

Time:11-05

I play around with constexpr and try to parse an ipv4 address into four digits, but it does not work. So i strip down the code to the point where it fails:

#include <cstdint>
#include <stdexcept>
#include <string_view>

#define WORKS 1

static constexpr uint8_t
getIpVal(const std::string_view &sv) noexcept {

    size_t pos = 0;
    auto len = sv.size();
    unsigned long val = 0;

    while (sv[pos] >= '0' && sv[pos] <= '9' && pos < len) {
        int digit = sv[pos] - '0';
        val *= 10;
        val  = digit;
        if (val > UINT8_MAX) {
            return 0;
            // throw std::invalid_argument(sv.data());
        }
          pos;
    }

    if (pos < len) {
            return 0;
        // throw std::invalid_argument(sv.data());
    }

    return val;
}

static constexpr auto
operator""_ipv4(const char *ipv4Ptr, const size_t size) {
    const std::string_view ipv4Str(ipv4Ptr, size);

    const auto pos1 = ipv4Str.find('.');
    if (pos1 == std::string_view::npos) {
        throw std::invalid_argument(ipv4Ptr);
    }
    
    const auto str1 = ipv4Str.substr(0, pos1);

#if WORKS
    return str1;
#else
    return getIpVal(str1);
#endif
}

auto
test1() {
    return "127.0.0.1"_ipv4;
}

auto
test2() {
    return getIpVal("127");
}

I'm trying to compile this using Compiler Explorer: https://godbolt.org/z/aY3ETo6ba

As long as WORKS is defined as 1, everything seems to work fine:

.LC0:
        .string "127.0.0.1"
test1():
        mov     eax, 3
        mov     edx, OFFSET FLAT:.LC0
        ret
test2():
        mov     eax, 127
        ret

But if I set it to zero, the compiler creates a complete code with a loop to evaluate the number. I do not understand why. The individual functions seem to work, but the combination fails. In my opinion it should just create two functions with numbers:

test1():
        mov     eax, 127
        ret
test2():
        mov     eax, 127
        ret

CodePudding user response:

Making a function constexpr means that a function might get evaluated at compile time. Whether the function is actually evaluated at compile time depends on whether it's being called during the evaluation of a constant.

In your example, you're not calling either ""_ipv4, or getIpVal in the process of evaluating a constant. As a result, whether the functions return the answer directly, or call a loop, depends on the compiler doing optimizations. e.g. Clang optimizes the code to generate assembly that returns values directly. https://godbolt.org/z/zz3oj3Mor

If you want to guarantee that a function is called during constant evaluation, you can assign the result to a constexpr variable, e.g.

constexpr auto n = getIpVal("127");

and now you'll get a compiler error message that:

while (sv[pos] >= '0' && sv[pos] <= '9' && pos < len)

is incorrect. This is because you're indexing into sv before checking if pos is a valid index. Normally this would be UB, but there's no UB at compile time, and you forced the compiler to evaluate the function at compile time, so you get a nice diagnostic.

Another way to guarantee that the function is called at compile time is to make it a consteval function. However, such functions can only be called at compile time, so this won't work if you need to call this with non-constant arguments (e.g. arguments that you get from the user).

  • Related