在 Lambda 捕获中构建 intializer_list<string>
Construction of an intializer_list<string> in a Lambda Capture
所以我在编造一个答案here and I needed to use C++14's identifier initializer within a lambda capture:
const auto cmp = [ordering = { "dog", "cat", "mouse", "elephant" }](const string& lhs, const string& rhs) { return find(cbegin(ordering), cend(ordering), lhs) < find(cbegin(ordering), cend(ordering), rhs); };
只要 ordering
是 intializer_list<const char*>
,这就可以正常工作。但是出于某种原因,如果我将其设为 intializer_list<string>
:
,一切都会崩溃
const auto cmp = [ordering = { "dog"s, "cat"s, "mouse"s, "elephant"s }](const string& lhs, const string& rhs) { return find(cbegin(ordering), cend(ordering), lhs) < find(cbegin(ordering), cend(ordering), rhs); };
我会说这是一个编译器错误,但我发现 Visual Studio 有一个更奇怪的问题,当使用 intializer_list<string>
时,一切都比较相等,但一切再次与 intializer_list<const char*>
,可以把测试代码复制到http://webcompiler.cloudapp.net自己看
这实际上是 gcc 和 Visual Studio 中的错误还是我做错了什么?
编辑:
给定的代码将使用 cmp
的这些定义之一:
map<string, int, function<bool(const string&, const string&)>> myMap(cmp);
myMap["cat"s] = 1;
myMap["dog"s] = 2;
myMap["elephant"s] = 3;
myMap["mouse"s] = 4;
myMap["rhino"s] = 5;
for (auto& i : myMap) {
cout << i.first << ' ' << i.second << endl;
}
在 Visual Studio 和 gcc 使用 intializer_list<const char*>
版本正确生成:
dog 2
cat 1
mouse 4
elephant 3
rhino 5
在 http://ideone.com 上使用 intializer_list<string>
错误地生成:
cat 1
dog 2
mouse 4
elephant 5
并且在 Visual Studio 上使用 intializer_list<string>
会错误地生成:
cat 5
恐怕这个错误在你的代码中。
问题是当您初始化 cmp
对象时,您是 copying/moving lambda,这意味着您失去了支持捕获的 initalizer_list
的数组的生命周期扩展。如果您通过引用绑定来延长 lambda 的生命周期,那么您的代码将至少适用于 ideone 和 coliru 上的 gcc 版本:
const auto& cmp = [ordering = { "dog"s, "cat"s, "mouse"s, "elephant"s }](const string& lhs, const string& rhs) { return find(cbegin(ordering), cend(ordering), lhs) < find(cbegin(ordering), cend(ordering), rhs); };
^-- extend lifetime of lambda and thereby captured initializer_list
请注意,这仅在 原始 生命周期延长的 lambda 至少与使用它的所有东西一样长的情况下才有效。例如,您将无法从封闭函数 return myMap
。
Per lifetime of a std::initializer_list return value the temporary array proxied by the initializer_list
is lifetime-extended to the initializer_list
object, the same as binding a reference to a temporary. If we assume that CWG 1695 适用于 initializer_list
s 与它适用于引用相同([dcl.init.list] 中的语言是initializer_list
生命周期延长的行为“完全像将引用绑定到临时 ”)然后 initializer_list
init-capture仅当封闭的 lambda 有效时才有效,并且仅当封闭的 lambda 本身通过引用绑定延长生命周期时才会在封闭范围内持续存在。
请注意,您的代码在 clang 中仍然无法工作,因为 clang does not implement CWG 1695,在 MSVC 中也不行,我假设出于同样的原因(我尝试了 CWG 1695 中的示例并得到了相同的结果;我没有尚未找到 MSVC 的错误报告)。
您使用 initializer_list<char const*>
的代码可以工作,因为 initializer_list
原始(普通)类型的支持数组可以被视为常量;您仍在访问悬空引用,但它恰好具有所需的值。我猜想 coliru 上的 gcc 版本正在选择将支持数组放置在被破坏的 string
s 仍然可以使用它们的预破坏值访问的地方;无论如何,您仍在访问已破坏的对象。
所以我在编造一个答案here and I needed to use C++14's identifier initializer within a lambda capture:
const auto cmp = [ordering = { "dog", "cat", "mouse", "elephant" }](const string& lhs, const string& rhs) { return find(cbegin(ordering), cend(ordering), lhs) < find(cbegin(ordering), cend(ordering), rhs); };
只要 ordering
是 intializer_list<const char*>
,这就可以正常工作。但是出于某种原因,如果我将其设为 intializer_list<string>
:
const auto cmp = [ordering = { "dog"s, "cat"s, "mouse"s, "elephant"s }](const string& lhs, const string& rhs) { return find(cbegin(ordering), cend(ordering), lhs) < find(cbegin(ordering), cend(ordering), rhs); };
我会说这是一个编译器错误,但我发现 Visual Studio 有一个更奇怪的问题,当使用 intializer_list<string>
时,一切都比较相等,但一切再次与 intializer_list<const char*>
,可以把测试代码复制到http://webcompiler.cloudapp.net自己看
这实际上是 gcc 和 Visual Studio 中的错误还是我做错了什么?
编辑:
给定的代码将使用 cmp
的这些定义之一:
map<string, int, function<bool(const string&, const string&)>> myMap(cmp);
myMap["cat"s] = 1;
myMap["dog"s] = 2;
myMap["elephant"s] = 3;
myMap["mouse"s] = 4;
myMap["rhino"s] = 5;
for (auto& i : myMap) {
cout << i.first << ' ' << i.second << endl;
}
在 Visual Studio 和 gcc 使用 intializer_list<const char*>
版本正确生成:
dog 2
cat 1
mouse 4
elephant 3
rhino 5
在 http://ideone.com 上使用 intializer_list<string>
错误地生成:
cat 1
dog 2
mouse 4
elephant 5
并且在 Visual Studio 上使用 intializer_list<string>
会错误地生成:
cat 5
恐怕这个错误在你的代码中。
问题是当您初始化 cmp
对象时,您是 copying/moving lambda,这意味着您失去了支持捕获的 initalizer_list
的数组的生命周期扩展。如果您通过引用绑定来延长 lambda 的生命周期,那么您的代码将至少适用于 ideone 和 coliru 上的 gcc 版本:
const auto& cmp = [ordering = { "dog"s, "cat"s, "mouse"s, "elephant"s }](const string& lhs, const string& rhs) { return find(cbegin(ordering), cend(ordering), lhs) < find(cbegin(ordering), cend(ordering), rhs); };
^-- extend lifetime of lambda and thereby captured initializer_list
请注意,这仅在 原始 生命周期延长的 lambda 至少与使用它的所有东西一样长的情况下才有效。例如,您将无法从封闭函数 return myMap
。
Per lifetime of a std::initializer_list return value the temporary array proxied by the initializer_list
is lifetime-extended to the initializer_list
object, the same as binding a reference to a temporary. If we assume that CWG 1695 适用于 initializer_list
s 与它适用于引用相同([dcl.init.list] 中的语言是initializer_list
生命周期延长的行为“完全像将引用绑定到临时 ”)然后 initializer_list
init-capture仅当封闭的 lambda 有效时才有效,并且仅当封闭的 lambda 本身通过引用绑定延长生命周期时才会在封闭范围内持续存在。
请注意,您的代码在 clang 中仍然无法工作,因为 clang does not implement CWG 1695,在 MSVC 中也不行,我假设出于同样的原因(我尝试了 CWG 1695 中的示例并得到了相同的结果;我没有尚未找到 MSVC 的错误报告)。
您使用 initializer_list<char const*>
的代码可以工作,因为 initializer_list
原始(普通)类型的支持数组可以被视为常量;您仍在访问悬空引用,但它恰好具有所需的值。我猜想 coliru 上的 gcc 版本正在选择将支持数组放置在被破坏的 string
s 仍然可以使用它们的预破坏值访问的地方;无论如何,您仍在访问已破坏的对象。