在 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); };

只要 orderingintializer_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); };

Live Example

我会说这是一个编译器错误,但我发现 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_lists 与它适用于引用相同([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 版本正在选择将支持数组放置在被破坏的 strings 仍然可以使用它们的预破坏值访问的地方;无论如何,您仍在访问已破坏的对象。