Search code examples
c++c++11visual-studio-2013language-lawyermsvc12

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


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)?


Solution

  • 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};