组合中的 constexpr 评估失败

constexpr evaluation in combination failed

提问人:Marcus Hampel 提问时间:11/5/2023 更新时间:11/5/2023 访问量:85

问:

我尝试将 ipv4 地址解析为四位数,但它不起作用。所以我把代码剥离到失败的程度:constexpr

#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");
}

我正在尝试使用编译器资源管理器编译它: https://godbolt.org/z/aY3ETo6ba

只要定义为 ,一切似乎都很好:WORKS1

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

但是,如果我将其设置为零,编译器会创建一个完整的代码,其中包含一个循环来计算该数字。我不明白为什么。 各个函数似乎有效,但组合失败。在我看来,它应该只创建两个带有数字的函数:

test1():
        mov     eax, 127
        ret
test2():
        mov     eax, 127
        ret
C++ C++17 常量

评论


答:

6赞 cigien 11/5/2023 #1

创建函数意味着函数可能会在编译时进行计算。函数是否在编译时实际计算取决于在常量的计算期间是否调用该函数。constexpr

在您的示例中,您既没有调用 ,也没有在计算常量的过程中调用。因此,函数是直接返回答案,还是调用循环,取决于编译器是否进行优化。例如,Clang 优化代码以生成直接返回值的程序集。https://godbolt.org/z/zz3oj3Mor""_ipv4getIpVal

如果要保证在常量求值期间调用函数,可以将结果分配给变量,例如constexpr

constexpr auto n = getIpVal("127");

现在,您将收到一条编译器错误消息:

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

不正确。这是因为在检查索引是否有效之前,您正在编制索引。通常情况下,这将是 UB,但在编译时没有 UB,并且您强制编译器在编译时评估函数,因此您可以得到一个很好的诊断。svpos

保证在编译时调用函数的另一种方法是使其成为函数。但是,此类函数只能在编译时调用,因此,如果您需要使用非常量参数(例如,从用户那里获得的参数)调用它,这将不起作用。consteval

评论

2赞 0xC0000022L 11/5/2023
consteval我认为从 C++20 开始也有帮助。
3赞 cigien 11/5/2023
@0xC0000022L 确实如此。我添加了有关如何执行此操作的解释。
1赞 Deniz Bahadir 11/5/2023 #2

cigien回答几乎可以解释这一点。

对于那些对工作解决方案以及问题和解决方案的口头解释感兴趣的人,我在 Meeting C++ 2021 会议上做了一个简短的闪电演讲,通过一个如何为 IPv4 地址实现用户定义文字的示例来解释和演示这一点:

https://youtu.be/KmoTKz95Wsg