Construction of an intializer_list<string> in a Lambda Capture

强颜欢笑 提交于 2019-12-24 02:04:48

问题


So I was cooking up an answer 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); };

And this works fine as long as ordering is an intializer_list<const char*>. But for some reason everything falls apart if I make it an 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

I would say this was a compiler bug, but I'm seeing an even weirder issue with Visual Studio where everything compares equal when using the intializer_list<string>, but everything again works fine with an intializer_list<const char*>, you can copy the test code to http://webcompiler.cloudapp.net to see for yourself.

Is this actually a bug in gcc and Visual Studio or have I done something wrong?

EDIT:

Given code that will use one of these definitions of 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;
}

On Visual Studio and gcc using the intializer_list<const char*> version correctly generates:

dog 2
cat 1
mouse 4
elephant 3
rhino 5

Using the intializer_list<string> on http://ideone.com incorrectly generates:

cat 1
dog 2
mouse 4
elephant 5

And using the intializer_list<string> on Visual Studio incorrectly generates:

cat 5


回答1:


The bug is in your code, I'm afraid.

The problem is that you're copying/moving the lambda when you initialize the cmp object, which means that the you lose lifetime extension on the array backing the captured initalizer_list. If you lifetime-extend the lambda by reference binding then your code will work at least for the versions of gcc on ideone and coliru:

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

Note that this will only work if the original lifetime-extended lambda lives for at least as long as everything that uses it. For example, you would not be able to return myMap from the enclosing function.

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 applies to initializer_lists the same as it applies to references (and the language in [dcl.init.list] is that initializer_list lifetime extension behaves "exactly like binding a reference to a temporary") then an initializer_list init-capture is only valid as long as the enclosing lambda is valid, and will only persist through the enclosing scope if the enclosing lambda is itself lifetime-extended via reference binding.

Note that your code will still not work in clang, because clang does not implement CWG 1695, and nor in MSVC, I assume for the same reason (I tried the example in CWG 1695 and got the same result; I haven't yet found a bug report for MSVC).

Your code with initializer_list<char const*> works because the backing array for initializer_lists of primitive (trivial) types can be treated as constant; you're still accessing a dangling reference, but it happens to have the desired value. I'd guess that the version of gcc on coliru is choosing to place the backing array somewhere the destructed strings can still be accessed with their pre-destruction values; regardless, you're still accessing destructed objects.



来源:https://stackoverflow.com/questions/37621932/construction-of-an-intializer-liststring-in-a-lambda-capture

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!