Same clang, different results for std::initializer_list program with -std=c++14/-std=c++17
Asked Answered
T

1

7

First of all this is curiosity kind of question, I would never write code like this in real life.

Following code behaves differently with -O3 -std=c++14 and -O3 -std=c++17 flags, in C++14 I get bad alloc, I presume from copy construction from a garbage 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

My guess is that C++17 version keeps the underlying array alive longer than C++14 version, but I found no relevant changes to initializer list from C++14 to C++17 on cppreference so I am confused. Is this just UB being UB, or did language change?

P.S. I know how to fix this, using static const auto& results works, like mentioned before this is just a question about corner cases of the language.

Twombly answered 17/12, 2019 at 18:36 Comment(2)
AFAIK the change of the lifetime was from C++11 to C++14. The code should be fine in C++14 or C++17 so not sure what is going on. GCC works in 14 or 17 mode.Preparator
Also works for 14 and 17 on MSVC.Viv
R
7

This has to do with guaranteed copy elision, a new language feature in C++17.

This line (reduced):

static auto results = std::initializer_list<string>{x, y};

In C++14 constructs an initializer list and then moves it into results - which immediately dangles because initializer_list doesn't manage any lifetimes (a std::initializer_list has a backing const array with the same lifetime as the initial object - once the initial initializer_list is destroyed at the end of the line, so is the backing array).

In other words, in C++14, this program has undefined behavior.

In C++17 it behaves exactly like:

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

In this case, the backing array has the same lifetime as results, which is for the length of the program. This program has well-defined behavior.

Remus answered 17/12, 2019 at 18:50 Comment(5)
So only clang is correct here? Cppreference might need to be updated then since it states: The lifetime of the underlying array is the same as any other temporary object, except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporaryPreparator
@NathanOliver-ReinstateMonica Only clang is correct about what? What about cppreference needs to be updated?Remus
Never mind, I get it now. std::initializer_list<int> i3 = { 1, 2, 3 }; is fine but std::initializer_list<int> i3 = std::initializer_list<int>{ 1, 2, 3 }; leaves it dangling since the "array" was sucked up by the std::initializer_list temporary on the right hand side.Preparator
@NathanOliver-ReinstateMonica Yep. Only change is in 17, there's no temporary in the 2nd case.Remus
wow, this is insane, but makes sense, just I would not figure it out by myself in a thousand years although I knew about guaranteed cpy elision.Twombly

© 2022 - 2024 — McMap. All rights reserved.