Array member-initializers in GCC
The other day I ran across some code like this:
template<class T>
struct Holder {
T t_;
explicit Holder() : t_() {}
Holder(const Holder& rhs) : t_(rhs.t_) {}
~~~~
};
This was in an old codebase, which until recently had still been using
GCC’s -Weffc++
to enforce C++98 idioms such as the explicitly non-defaulted
copy constructor depicted above.
If you’re still using
-Weffc++
, please, stop using it!
We’d pulled this codebase forward to C++17 and GCC 12, and all was going swimmingly. We even enabled PC-Lint. And that’s when PC-Lint complained about a usage like this:
using Node = Holder<int[2]>;
Node n1;
Node n2 = n1;
h.cpp:5 error 21: array initializer must be an initializer list
h.cpp:5 supplemental 891: in instantiation of member function
'Holder<int [2]>::Holder' requested here
h.cpp:11 supplemental 897: in instantiation of function template
'Holder<int [2]>::Holder' triggered here
“Array initializer must be an initializer list”? Hm, I see: When T
is int[2]
,
t_
is an array, and indeed it’s not valid C++ to initialize an array with a
copy of another array.
int a[2];
int b[2] = a; // ERROR
int c[2](a); // ERROR
struct S { int d[2]; S() : d(a) {} }; // ERROR, but...
// accepted from GCC 4.7 until GCC 14
However, the GCC compiler itself didn’t flag d
as an error until GCC 14!
(This was GCC bug 54965.)
GCC from 4.7.x to 13.x would, as an “accidental extension” to standard C++,
allow you to initialize one array with another. Luckily, the codegen for this
is what you’d expect: it does indeed copy the contents of the array.
So that’s how we were able to use this “feature” for over a decade
without noticing anything amiss.
Furthermore, even though GCC 4.6.x rejected the code above, it (and going back at least as far as GCC 3.4, released in 2004) would still let you initialize one array with another as long as the initialization wasn’t trivial:
struct S {
std::string s_[2];
S(const S& rhs) : s_(rhs.s_) {}
// accepted from the dawn of time until GCC 14
};
But Clang, MSVC, and EDG have never accepted this extension, as far as I can tell; and neither has PC-Lint. So as we move to PC-Lint (and someday even to GCC 14), we have to clean up this copy constructor.
The correct fix here, of course, is to stop manually defining your copy constructors.
Write =default
instead.
struct S {
std::string s_[2];
S(const S& rhs) = default;
};
If you can’t =default
your copy constructor for some other reason, then you’ll just have
to turn the member-initializer into a default-initialization followed by a loop over assignment:
struct S {
std::string s_[2];
S(const S& rhs) {
std::copy(rhs.s_, rhs.s_ + 2, s_);
}
};
Alternatively, you could switch from T[N]
to std::array<T, N>
— although see
“Prefer core-language features over library facilities” (2022-10-16) —
because even though an array can’t be copy-initialized like this,
a struct containing an array (which is all std::array
is) can be.
struct S {
std::array<std::string, 2> s_;
S(const S& rhs) : s_(rhs.s_) {}
};
Notice that Holder
’s default constructor above is no problem (syntactically);
it’s totally valid to value-initialize an array data member with empty parentheses.
In fact, in this generic context, it’s probably the best option.
Of course in ordinary code you should prefer to use a default member initializer, so that you can default your zero-argument constructor:
struct TwoInts {
int data_[2] = {};
explicit TwoInts() = default;
};
But sadly C++ has no generic syntax that works to value-initialize both arrays and
non-arrays. You’d think = T()
would Just Work for arrays, like it works for all other
value-initializable types in the language:
template<class T>
struct Holder {
T t_ = T(); // fantasy syntax
explicit Holder() = default;
~~~~
};
but no, T()
is deliberately and surgically broken
when T
is an array type, for some unknown historical reason.
(And T{}
fails when T
is an array of ExplicitlyDefaultConstructible
,
because T{}
does list-initialization
instead of value-initialization.)
CWG914 asks for a paper to fix
this defect; no such paper has materialized yet.