I just received a reader email that asked about GotW #42:
You write "Non-Problem: Zero-Length Arrays Are Okay", but both 14882:2003 and N2914 "[dcl.array]" say "If the constant-expression (5.19) is present, it shall be an integral constant expression and its value shall be greater than zero.". Shall we assume that you overrule the standard? :-) Or am I missing something, like the meaning of "derived-declarator-type-list" (I can’t find it anywhere in 14882:2003)?
I thought the answer might be of interest to other people, so I’m posting it here.
First, this reader gets kudos for consulting not only the ISO C++ standard, but also the current C++0x working draft from June (paper N2914), to do research before asking the question. Good stuff.
He also gets a small penalty point, though, for reading only the subhead text that he quoted from GotW #42, and not the actual short passage the heading introduces. The article already contains the answer to his qustion, and that answer is still the same in the current C++0x draft all these years later:
From 5.3.4 [expr.new], paragraph 7:
When the value of the expression in a direct-new-declarator is zero, the allocation function is called to allocate an array with no elements. The pointer returned by the new-expression is non-null. [Note: If the library allocation function is called, the pointer returned is distinct from the pointer to any other object.]
So the reader is quoting from 8.3.4 [dcl.array], which governs non-heap allocated arrays (e.g., T array[N];). In that case, a zero length is not allowed by the standard. You can’t rely on it in portable code because although it is allowed as an extension in some popular compilers (e.g., Gnu gcc) it is treated as an error in others (e.g., Visual C++).
But the case being discussed in GotW #42 is dynamically allocating an array using the array form of new (e.g., new T[n] where n is zero), which is governed by 5.3.4 [expr.new]. Here, zero length is okay for the reasons given in GotW #42:
The result of "new T[0]" is just a pointer to an array with zero elements, and that pointer behaves just like any other result of "new T[n]" including the fact that you may not attempt to access more than n elements of the array… in this case, you may not attempt to access any elements at all, because there aren’t any.
"Well, if you can’t do anything with zero-length arrays (other than remember their address)," you may wonder, "why should they be allowed?" One important reason is that it makes it easier to write code that does dynamic array allocation. For example, the function f above would be needlessly more complex if it was forced to check the value of its n parameter before performing the "new T[n]" call.
To make GotW #42 completely unambiguous, it could more specifically say that zero-length heap-allocated arrays are okay, which was the case being discussed in the article. But it always helps to read more than the subhead text.
Why would the standard not allow zero length stack based arrays? This completely breaks my design.
struct Validator {
int location;
char contents;
};
static const Validator VALID[] =
{
{0, {‘a’, ‘b’, ‘c’}},
{3, {‘d’, ‘e’}},
{6, {}},
{7, {‘v’, ‘w’, ‘x’, ‘y’, ‘z’}},
};
template inline size_t ARRAY_SIZE(const T (&array)[N])
{
return N;
}
int main(int, char**) {
for (int i = 0; i < ARRAY_SIZE(VALID); i++)
{
cout << "The Contents at location " << VALID[i].location << " are:";
for (int j = 0; j < ARRAY_SIZE(VALID[i].contents; j++)
{
cout << VALID[i].contents[j] << ", ";
}
cout << endl;
}
}
There are good scenarios where you would want 0 length stack based arrays.
You can safely do (A+N), but theoretically, i think there are problems with &A[N] because A[N] is evaluated as an lvalue but it isn’t guaranteed to refer to an object. But “An lvalue refers to an object or function.” and there is no “empty lvalue”. If C++ had a rule like C that says in a &*X, the *X expression isn’t evaluated (like in a sizeof, where *(int*)0 of course is perfectly legal), this would be different.
> AFAIK, you can’t do it with an actual array of size 0 either.
You should be able to, since the Standard specifically allows to have a pointer one past the end of an array. I.e. for an array A of N elements, &A[N] is valid, even though dereferencing it is not. I don’t see why it should suddenly be invalid for N=0.
Re: Alex, no, you can’t do that.
AFAIK, you can’t do it with an actual array of size 0 either.
If you really want to write that memcpy line, you should do:
memcpy(pDest, vec.empty()?0:&vec[0], vec.size());
…but you could also just use std::copy ;)
Does this also apply to a vector of size 0? So, for example is this code correct if the vector is empty?
memcpy(pDest, &vec[0], vec.size());
VC++ throws an SEH if checked iterators are turned on. It makes code more cumbersome by having to check the size of vec before dereferencing it.
Thanks for the explanation. I’ve happened to used zero-length array before. It compiled(with VC++) and worked. So I never thought it could fail until I read this blog.
Now I recall that it was used something like this way,
void f(size_t n)
{
boost::shared_array si(new int[n]);
//…
}
It feels good that you realize you safely passed an edge only afterwards.