MSVC 12 std::initializer_list bug when copying std::string

210 views Asked by At

I'm trying to create a C++ program with MSVC 12 (Visual Studio 2013, Update 4) which uses a std::initializer_list of structs which have std::string members. I seem to have run into a bug in MSVC. Here's a minimal example which exhibits the problem:

#include <cassert>
#include <initializer_list>
#include <string>

namespace
{
    struct TestStructure
    {
        std::string m_string;
        int m_integer;

        TestStructure(const std::string& string, int integer)
            : m_string(string), m_integer(integer)
        {
        }

        TestStructure(const TestStructure&) = default;
        ~TestStructure() = default;
        TestStructure& operator=(const TestStructure&) = default;
    };
}

int main(int, char **)
{
    TestStructure structure("foobar", 12345);
    std::initializer_list<TestStructure> structures({structure});

    assert(structure.m_integer == 12345);
    assert(structure.m_string == "foobar");
    assert(structures.size() == 1);
    assert(structures.begin()->m_integer == 12345);
    assert(structures.begin()->m_string == "foobar"); // abort()'s here.

    return EXIT_SUCCESS;
}

I would expect that this program would compile and execute without any problems. However, when I run it the last assertion seems to fail. Looking in the Visual Studio debugger, it would seem that structures.begin()->m_string == "".

Is my program somehow not well-formed, or is this actually a bug in MSVC? Is there some workaround for this problem (other than just not using initializer lists)?

1

There are 1 answers

0
ecatmur On BEST ANSWER

The problem is that you're using both parentheses and braces:

std::initializer_list<TestStructure> structures({structure});
                                               ^^         ^^

This will construct a temporary std::initializer_list<TestStructure> and copy it to structures; the normal lifetime-extension will not be performed, so structures will be pointing to destructed storage:

[dcl.init.list]:

6 - The array has the same lifetime as any other temporary object (12.2), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary [...]

Note that clang agrees with MSVC on this; gcc performs lifetime extension on the backing array, but it is erroneous to do so (Bug filed: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66476).

If you want copy-initialization (with lifetime extension), use an equals sign:

std::initializer_list<TestStructure> structures = {structure};

Otherwise, use direct-list-initialization (using braces directly):

std::initializer_list<TestStructure> structures{structure};