相同的clang编译器,使用-std=c++14/-std=c++17编译std::initializer_list程序结果不同。

7
首先,这是一个好奇的问题,我在现实生活中不会编写这样的代码。
使用 -O3 -std=c++14 和 -O3 -std=c++17 标志时,以下代码的行为不同,在 C++14 中我会得到 bad alloc,我认为是从垃圾 std::string 的复制构造引起的:
#include<algorithm>
#include<numeric>
#include<vector>
#include<string>
#include<iostream>

using namespace std;
static auto results = std::initializer_list<string>{"1                               ",
"2"};
string f() {

    auto result = std::accumulate(results.begin(), results.end(), string(""));
    return result;

}

int main()
{
    return f().size();
}

https://godbolt.org/z/H-Xzei

我的猜测是,C++17版本比C++14版本保留了底层数组更长的时间,但我在cppreference上没有找到有关初始化列表从C++14到C++17的相关变化,所以我很困惑。这只是UB存在的问题,还是语言发生了变化?
附言:我知道如何修复这个问题,使用"static const auto& results"可以解决,就像之前提到的那样,这只是关于语言的边角情况的问题。

2
据我所知,从C++11到C++14有生命周期的变化。代码在C++14或C++17中应该是正常的,所以不确定出了什么问题。GCC可以在14或17模式下工作。 - NathanOliver
1
同样适用于 MSVC 上的 14 和 17。 - João Paulo
1个回答

7
这与C++17的新语言特性——拥有保证的复制省略有关。
这行代码(简化后):
static auto results = std::initializer_list<string>{x, y};

在C++14中,创建一个初始化器列表,然后将其移动到results中 - 立即变得无效,因为initializer_list不管理任何的生命周期(一个std::initializer_list具有与初始对象相同的生存期的支持const数组 - 一旦初始initializer_list在行末被销毁,支持的数组也会被销毁)。

换句话说,在C++14中,此程序的行为是未定义的。

在C++17中,它的行为与以下代码完全相同:

static std::initalizer_list<string> results{x, y};

在这种情况下,支持数组的生命周期与 results 相同,即整个程序的生命周期。该程序具有良好定义的行为。

这里只有clang是正确的吗?Cppreference可能需要更新,因为它声明:底层数组的生命周期与任何其他临时对象相同,除了从数组初始化initializer_list对象会像绑定到临时对象的引用一样延长数组的生命周期 - NathanOliver
@NathanOliver-ReinstateMonica 只有clang是正确的,关于什么?cppreference需要更新什么? - Barry
没关系,我现在明白了。std::initializer_list<int> i3 = { 1, 2, 3 }; 是可以的,但是 std::initializer_list<int> i3 = std::initializer_list<int>{ 1, 2, 3 }; 会导致悬空,因为右侧的 std::initializer_list 临时对象吞噬了这个“数组”。 - NathanOliver
@NathanOliver-ReinstateMonica 是的。唯一的变化是在第17行,第二种情况中没有临时变量。 - Barry
哇,这太疯狂了,但是很有道理,尽管我知道保证复制省略,但我自己在一千年内也想不出来。 - NoSenseEtAl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接