MSVC bug? – Returned inner lambda with non-init-capture of outer lambda's by-ref parameter causes C2131 when outer lambda is passed a plain function

huangapple go评论82阅读模式
英文:

MSVC bug? - Returned inner lambda with non-init-capture of outer lambda's by-ref parameter causes C2131 when outer lambda is passed a plain function

问题

这是演示问题的最小示例,其中包含错误的现场以及一些可能的解决方法:

constexpr auto foo = [](auto const& p) { return 

{ return 1; }; }; void bar(){} constexpr auto baz = foo(bar); // C2131

错误信息如下:

<source>(6): error C2131: expression did not evaluate to a constant
<source>(2): note: failure was caused by a read of an uninitialized symbol
<source>(2): note: see usage of '<lambda_833637519c89a13e2555ac846bc40c61>::()::<lambda_13c809781a1aa438f693875c73cfce81>::p'
<source>(6): note: the call stack of the evaluation (the oldest call first) is
<source>(6): note: while evaluating function '<lambda_833637519c89a13e2555ac846bc40c61>::()::<lambda_13c809781a1aa438f693875c73cfce81> <lambda_833637519c89a13e2555ac846bc40c61>::operator()<void(void)>(void (__cdecl &)(void)) const'
<source>(2): note: while evaluating function '<lambda_833637519c89a13e2555ac846bc40c61>::()::<lambda_13c809781a1aa438f693875c73cfce81>::<lambda_13c809781a1aa438f693875c73cfce81>(void (__cdecl &)(void))'
Compiler returned: 2

一些可能的解决方法:

  • bar 改为函数对象,即 constexpr auto bar = []{};
  • foo 的参数类型更改为按值传递,即 constexpr auto foo = [](auto p) { return

    { return 1; }; };

  • 对内部 lambda 使用初始化捕获,即 constexpr auto foo = [](auto const& p) { return

    { return 1; }; };

  • baz 设为非 constexpr,即 const auto baz = foo(bar);

这个问题是MSVC的一个bug,或者是那些接受上述代码的编译器中的一个非一致性问题。您提到的MSVC版本19.35存在此问题,但版本19.14似乎没有这个问题。

英文:

Here's the live minimal example demonstrating the issue, copied below along with error:

constexpr auto foo = [](auto const&amp; p) { return 

{ return 1; }; }; void bar(){} constexpr auto baz = foo(bar); // C2131

&lt;source&gt;(6): error C2131: expression did not evaluate to a constant
&lt;source&gt;(2): note: failure was caused by a read of an uninitialized symbol
&lt;source&gt;(2): note: see usage of &#39;&lt;lambda_833637519c89a13e2555ac846bc40c61&gt;::()::&lt;lambda_13c809781a1aa438f693875c73cfce81&gt;::p&#39;
&lt;source&gt;(6): note: the call stack of the evaluation (the oldest call first) is
&lt;source&gt;(6): note: while evaluating function &#39;&lt;lambda_833637519c89a13e2555ac846bc40c61&gt;::()::&lt;lambda_13c809781a1aa438f693875c73cfce81&gt; &lt;lambda_833637519c89a13e2555ac846bc40c61&gt;::operator ()&lt;void(void)&gt;(void (__cdecl &amp;)(void)) const&#39;
&lt;source&gt;(2): note: while evaluating function &#39;&lt;lambda_833637519c89a13e2555ac846bc40c61&gt;::()::&lt;lambda_13c809781a1aa438f693875c73cfce81&gt;::&lt;lambda_13c809781a1aa438f693875c73cfce81&gt;(void (__cdecl &amp;)(void))&#39;
Compiler returned: 2

Some possible workarounds:

  • make bar a function object, i.e. constexpr auto bar = []{};
  • change foo's param type to be by-value, i.e. constexpr auto foo = [](auto p) { return

    { return 1; }; };

  • use init-capture for the inner lambda, i.e. constexpr auto foo = [](auto const&amp; p) { return

    { return 1; }; };

  • make baz non-constexpr, i.e. const auto baz = foo(bar);

Is it indeed a bug in MSVC¹ or a non-conformance in the compilers that accept the code above?


(¹) I've used x64 msvc v19.35, but x64 msvc v19.14 seems to be ok with the code.

答案1

得分: 3

代码中的翻译如下:

  • "The type of such a data member is the referenced type if the entity is a reference to an object, an lvalue reference to the referenced function type if the entity is a reference to a function, or the type of the corresponding captured entity otherwise." 翻译为:如果实体是对象的引用,则此数据成员的类型为引用类型;如果实体是函数的引用,则此数据成员的类型为对引用函数类型的左值引用;否则,此数据成员的类型为对应捕获实体的类型。

  • "So with the closure types explicitly written out, it might look something like this:" 翻译为:因此,如果明确写出闭包类型,它可能看起来像这样:

  • "Which also doesn't compile in MSVC" 翻译为:这在 MSVC 中也不能编译通过。

  • "It seems like the MSVC bug is that it can't use structs with function references in constant expressions" 翻译为:看起来 MSVC 的问题是它无法在常量表达式中使用具有函数引用的结构体。

  • "The workaround is to capture a function pointer.

    does this because it is equivalent to auto lambda_member_p = p, where lambda_member_p is deduced to have a function pointer type if p is a function reference." 翻译为:解决方法是捕获一个函数指针。

    这样做是因为它等价于 auto lambda_member_p = p,其中 lambda_member_p 如果 p 是函数引用,则被推断为具有函数指针类型。

英文:

[expr.prim.lambda.capture]p10:

> The type of such a data member is the referenced type if the entity is a reference to an object, an lvalue reference to the referenced function type if the entity is a reference to a function, or the type of the corresponding captured entity otherwise.

(emphasis mine)

So with the closure types explicitly written out, it might look something like this:

struct __foo_lambda_type {
    template&lt;typename T&gt;
    constexpr auto operator()(T const&amp; p) const {
        struct __inner_lambda_type {
            // (p is a reference to a function, capture by value creates a reference)
            T&amp; p;

            constexpr auto operator()() const {
                return 1;
            }
        };
        return __inner_lambda_type{ p };
    };
};

constexpr auto foo = __foo_lambda_type{};

void bar(){}
constexpr auto baz = foo.operator()&lt;void()&gt;(bar);

... Which also doesn't compile in MSVC

It seems like the MSVC bug is that it can't use structs with function references in constant expressions

https://godbolt.org/z/5E6KP6boP

void bar();

struct function_reference {
    void(&amp;ref)();
    constexpr function_reference(void(&amp;ctor_ref)()) :
        ref(ctor_ref)  // line (6)
        {}
};

constexpr auto f(void(&amp;local_ref)()) {
    auto x = function_reference{local_ref};  // line (11)
    return x;
};

constexpr auto x = f(bar);  // line (15)
(15): error C2131: expression did not evaluate to a constant
( 6): note: failure was caused by a read of an uninitialized symbol
( 6): note: see usage of &#39;function_reference::ref&#39;
(15): note: the call stack of the evaluation (the oldest call first) is
(15): note: while evaluating function &#39;function_reference f(void (__cdecl &amp;)(void))&#39;
(11): note: while evaluating function &#39;function_reference::function_reference(void (__cdecl &amp;)(void))&#39;

The workaround is to capture a function pointer.

does this because it is equivalent to auto lambda_member_p = p, where lambda_member_p is deduced to have a function pointer type if p is a function reference.

huangapple
  • 本文由 发表于 2023年7月18日 16:41:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/76710945.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定