Search code examples
c++c++11gccclanginitializer-list

std::initializer_list destructs the containing objects before the scope end in clang


As shown in the following code, std::initializer_list's containing objects get destroyed even before the scope finishes. I would like to know whether the code is not complying with the standard or it is a bug in clang (as it works just fine with gcc and vc).

#include <iostream>

class Attrib
{
public:
    Attrib()
    {
        std::cout<< "Constructor called"<< std::endl;
    }

    Attrib(const Attrib& r)
    {
        std::cout<< "Copy constructor called"<< std::endl;
    }

    ~Attrib()
    {
        std::cout<< "Destructor called"<< std::endl;
    }
};

int main()
{
    auto attribs = std::initializer_list<Attrib>({Attrib()});
    std::cout<< "Before returning from main"<< std::endl;
    return 0;
}

Output in clang is,

Constructor called
Destructor called
Before returning from main

Output in gcc is,

Constructor called
Before returning from main
Destructor called

EDIT: If we modify the initializer_list creation line in main by a bit like this,

     std::initializer_list<Attrib> attribs = {Attrib()};

Output in CLang is,

Constructor called
Before returning from main
Destructor called

Solution

  • This is a bug in Clang. Possibly 31636 or 40562.

    auto attribs = std::initializer_list<Attrib>({Attrib()});
    

    This is copy-initializing attribs from the initializer std::initializer_list<Attrib>({Attrib()}). Following C++17 guaranteed copy elision, this is equivalent to copy-initializing attribs from {Attrib()}.

    [dcl.init.list]

    ¶5 An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation generated and materialized a prvalue of type "array of N const E", where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array.

    ¶6 The array has the same lifetime 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 temporary.

    According to CWG1565:

    Q. If an initializer_list object is copied and the copy is elided, is the lifetime of the underlying array object extended?

    A. The consensus of CWG was that the behavior should be the same, regardless of whether the copy is elided or not.

    As you observed in your EDIT, Clang behaves correctly when we remove the redundant "copy":

    auto attribs = std::initializer_list<Attrib>{Attrib()};