c++ 带有consteval关键字的Lambda在LLVM工作时会导致GCC和MSVC错误,谁是正确的?

oo7oh9g9  于 2023-04-08  发布在  其他
关注(0)|答案(1)|浏览(132)

我在MSVC 19.33和GCC 12.2中得到了以下代码的错误。
LLVM 16(clang++ with libc++)编译它。godbolt LLVM

#include <string>
#include <string_view>

int main() {
    static constexpr auto enlarge =
        [](std::string&& target, std::size_t const new_size) consteval {
            auto const old_size = target.size();
            if(new_size < old_size) {
                throw "new size is smaller then old size";
            }
            target.resize(new_size);
            target[old_size] = 0;
            return target.c_str();
        };

    using namespace std::literals;
    static_assert(enlarge("test"s, 32) == "test"sv);
}

GCC 12.2(g++ & libstdc++)抱怨:godbolt GCC

In file included from /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/string:41,
                 from <source>:1:
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/allocator.h: In function 'int main()':
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/allocator.h:185:52: error: 'enlarge.main()::<lambda(std::string&&, std::size_t)>(std::literals::string_literals::operator""s(const char*, std::size_t)(4), 32)' is not a constant expression because it refers to a result of 'operator new'
  185 |             return static_cast<_Tp*>(::operator new(__n));
      |                                      ~~~~~~~~~~~~~~^~~~~

MSVC 19.33投诉:MS Godbolt MSVC

<source>(17): error C7595: 'main::<lambda_1>::operator ()': call to immediate function is not a constant expression
<source>(17): note: (sub-)object points to memory which was deallocated during constant evaluation
<source>(17): error C2446: '==': no conversion from 'std::string_view' to 'int'
<source>(17): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

如果我从lambda中删除关键字consteval,它可以在所有三个编译器上使用相应的标准库(LLVMGCC,MSVC)。
这是GCC和MSVC中的一个bug,还是consteval对执行有另一种影响,而不是constexpr上下文中的调用,LLVM接受这一点是错误的?
我假设这是一个单独的问题,但为了完整性:当我运行clang++ 16和libstdc++ 12.2时,无论consteval如何,我都会得到错误:godbolt clang++ with libstdc++

<source>:17:19: error: static assertion expression is not an integral constant expression
    static_assert(enlarge("test"s, 32) == "test"sv);
                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/basic_string.h:620:2: note: undefined function '_M_construct<const char *>' cannot be used in a constant expression
        _M_construct(__s, __s + __n, std::forward_iterator_tag());
        ^
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/basic_string.h:4330:14: note: in call to 'basic_string(&"test"[0], 4, std::allocator<char>())'
    { return basic_string<char>{__str, __len}; }
             ^
<source>:17:27: note: in call to 'operator""s(&"test"[0], 4)'
    static_assert(enlarge("test"s, 32) == "test"sv);
                          ^
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/basic_string.h:330:9: note: declared here
        _M_construct(_FwdIterator __beg, _FwdIterator __end,
        ^
2ul0zpep

2ul0zpep1#

通过P2564R3对当前C++23草案所做的更改,代码是格式良好的,因为enlarge("test"s, 32)不是 * 立即调用 *,因为它是 * 显式常量求值表达式 *(static_assert的操作数)的子表达式。
然而,如果没有这个改变,MSVC和GCC拒绝代码是正确的,Clang接受它是错误的。如果没有这个改变,enlarge("test"s, 32)是一个立即调用,立即调用形成完整表达式。这意味着在评估期间创建的临时对象在评估的最后一步被销毁。
因此,target引用的临时对象在计算enlarge("test"s, 32)的最后一步被销毁。然而,您使用return target.c_str();返回了指向此对象的指针。立即调用必须是 * 常量表达式 *,但无效指针不是 * 常量表达式 * 的允许结果。
这就是MSVC和GCC所抱怨的。两个错误消息都特别抱怨返回一个指向已释放内存的指针,但如果没有涉及动态内存分配,例如因为SSO就足够了,那么这也应该成立。

相关问题