英文:
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& 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
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& 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
, wherelambda_member_p
is deduced to have a function pointer type ifp
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<typename T>
constexpr auto operator()(T const& p) const {
struct __inner_lambda_type {
// (p is a reference to a function, capture by value creates a reference)
T& 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()<void()>(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(&ref)();
constexpr function_reference(void(&ctor_ref)()) :
ref(ctor_ref) // line (6)
{}
};
constexpr auto f(void(&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 'function_reference::ref'
(15): note: the call stack of the evaluation (the oldest call first) is
(15): note: while evaluating function 'function_reference f(void (__cdecl &)(void))'
(11): note: while evaluating function 'function_reference::function_reference(void (__cdecl &)(void))'
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论