2

Let's consider this class:

class A {
public:
    A() = delete;

    A(int i) :
            i_m(i) {
        std::cout << __PRETTY_FUNCTION__ << ' ' << i_m << '\n';
    }

    ~A() {
        std::cout << __PRETTY_FUNCTION__ << ' ' << i_m << '\n';
    }

private:
    int i_m{1234567890};
};

The default constructor is explicitly deleted so AFAIK A can only be constructed from an integer. The default initialization of data member i_m will never be used.

Let'ts consider this program:

int main() {
    using T = std::array<A, 2>;

    //T a;
    // error: use of deleted function 'std::array<A, 2>::array()'
    // note: 'std::array<A, 2>::array()' is implicitly deleted because the default definition would be ill-formed
    // error: use of deleted function 'A::A()'

    //T b{};
    // error: use of deleted function 'A::A()'
}

Again, this seems completely fine to me.

Let's now consider this other program:

int main() {
    using T = std::array<A, 2>;
    auto foo = new T{};
    delete foo;

    auto foo_init = new T{1, 2};
    delete foo_init;

//  auto zorg = new T();
//  delete zorg;
//  error: use of deleted function 'std::array<A, 2>::array()'
//  note: 'std::array<A, 2>::array()' is implicitly deleted because the default definition would be ill-formed:
//  error: use of deleted function 'A::A()'

    auto zorg_init = new T({3, 4});
    delete zorg_init;
}

This code does compile (with no warning) and generates the following output:

A::~A() 0
A::~A() 38870160
A::A(int) 1
A::A(int) 2
A::~A() 2
A::~A() 1
A::A(int) 3
A::A(int) 4
A::~A() 4
A::~A() 3

And now something seems not fine to me. How it that possible that the line auto foo = new T{}; is not considered ill-formed? The initialization of class A is completely bypassed here.

It is worth nothing that this code doesn't compile:

int main() {
    auto p = new A{};
    delete p;
}

The error being the expected:

error: use of deleted function 'A::A()'

I tested those codes with the following options: -Wall -Wextra -pedantic -std=c++17. gcc -v gives: gcc version 7.2.0 (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) on my computer.

Any explanation will be greatly appreciated :)


PS : after exhaustive tests in Compiler Explorer (using with sample https://godbolt.org/z/5VZLU_), it seems that this is an issue in gcc 7.x. Indeed, only 7.x versions of gcc compile this sample. gcc 6.4 doesn't not. gcc 8.1 neither. clang neither. msvc neither.

Can you confirm that there is no UB here and this is really a compiler bug?

Bktero
  • 722
  • 5
  • 15

1 Answers1

0

The fact that T b{}; doesn't compile while new T{} makes it evident that this is a compiler bug anyway. However, the standard itself is probably defective. Here's why.


The technical answer to this question is: the code is well-formed, because the standard specifically says so. [array.overview]/2

An array is an aggregate that can be list-initialized with up to N elements whose types are convertible to T.

The initializer {} indeed contains "up to N elements whose types are convertible to T" — it contains 0 elements, all of which are convertible to A.


How are the elements initialized then? The standard isn't really clear on this. Here's what [array.overview]/3 says:

An array satisfies all of the requirements of a container and of a reversible container ([container.requirements]), except that a default constructed array object is not empty and that swap does not have constant complexity. [...]

(emphasis mine)

The standard does not mention how the elements of this non-empty array are initialized.


Suppose that std::array is implemented like this (for N == 2):

template <class T, std::size_t N>
struct array {
    T __elem[N];
};

Then the elements are copy-initialized from {} according to [dcl.init.aggr]/8:

If there are fewer initializer-clauses in the list than there are elements in a non-union aggregate, then each element not explicitly initialized is initialized as follows:

  • (8.1) If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.

  • (8.2) Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).

  • (8.3) Otherwise, the program is ill-formed.

[...]

which is ill-formed according to [dcl.init.list]/3 (the deleted default constructor is selected).

This means that the standard considers the common implementation to be wrong, which is probably not intended.

L. F.
  • 19,445
  • 8
  • 48
  • 82