10

I recently wrote a function template which takes a reference to a C-array:

template <class T, size_t N>
void foo(T(&c_array)[N]);

Assuming T is a char, the length of the C-string is N - 1 due to the null-terminator. I realized I should probably handle the edge-case where N == 0, because then N - 1 would be std::numeric_limits<std::size_t>::max().

So in order to avoid the chaos that might ensue in the rare case that someone passes a zero-length array to this function, I placed a check for N == 0.

However, to my surprise, it seems that a zero-length array is actually not even an array type - or at least, that's what GCC seems to believe. In fact, a zero-length array doesn't even bind to the above function signature, if a function with a pointer-type signature is available as a candidate.

Consider the following code:

template <class T, size_t N>
void foo(T(&array)[N])
{
    std::cout << "Array" << std::endl;
}

void foo(const void* p)
{
    std::cout << "Pointer" << std::endl;
}

int main(int argc, char** argv)
{
    char array1[10] = { };
    const char* pointer = 0;
    char array2[0] = { };

    foo(array1);
    foo(pointer);
    foo(array2);
}

With GCC 4.3.2, this outputs:

Array
Pointer
Pointer

Oddly, the zero-length array prefers to bind to the function that takes a pointer type. So, is this a bug in GCC, or is there some obscure reason mandated by the C++ standard why this behavior is necessary?

Charles Salvia
  • 52,325
  • 13
  • 128
  • 140
  • Out of curiosity, what is the output of `printf("%p\n", array2);`? – cdhowie Nov 26 '10 at 22:12
  • In C, a zero-length array type is not permitted. I'm not sure about C++. I'm not sure if this is related to your problem. – Oliver Charlesworth Nov 26 '10 at 22:12
  • 1
    @Oli Charlesworth: In C you can use a zero-sized array as the last element of a struct. You can't instantiate such a struct ,but you can cast a chunk of bytes to a pointer to such a struct and access that array within it with an index – engf-010 Nov 26 '10 at 23:20
  • Zero length arrays *do* exist in C++. Their type just can't be constructed by the usual `T[N]` construct. Notably that type *can* be constructed though by `new`, so `new int[0]` is perfectly valid. – Johannes Schaub - litb Nov 27 '10 at 02:40
  • @Johannes Schaub - I would say zero length arrays *don't* exist in C++. `new int[0]` doesn't return an array-type, but rather, a pointer type. – Charles Salvia Nov 27 '10 at 02:45
  • @Charles you are wrong. It doesn't matter what type `new int[0]` evaluates to. That expression creates an object whose type is an array of zero length, so they necessarily have to exist. It's [well known](http://herbsutter.com/2009/09/02/when-is-a-zero-length-array-okay/) that there [exist zero length arrays](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#982). – Johannes Schaub - litb Nov 27 '10 at 03:40
  • @Johannes Schaub, it looks like `new int[0]`, as well as `malloc(0)` return a non-null pointer to a region in the heap. But what does it mean to say they create an "object whose type is an array of zero-length"? The only type created that is meaningful to the C++ type-system is a *pointer* type. In other words, the result of `new int[0]` won't bind to a function `template void foo(T(&)[N]);`. It will only bind to a function such as `void foo(int*);` So what does it really mean to say that `new int[0]` creates an array-type of zero-length? – Charles Salvia Nov 27 '10 at 05:22
  • Rather, isn't it more accurate to say that `new int[0]` creates a pointer to a zero-length buffer on the heap? – Charles Salvia Nov 27 '10 at 05:28
  • @Charles you assume that types are just a compile time component. But they aren't. Types describe objects and expressions. An object is created on the heap, and that object certainly isn't a pointer. A container of zero integers is rather more like the inverse of a pointer. The expression that `new` evaluates to is described by a pointer type, but that's nothing to do with the type of the object created. To the C++ type system, that object type is important. Otherwise, `delete (new int[0]);` wouldn't be undefined behavior. – Johannes Schaub - litb Nov 27 '10 at 17:11
  • `new` is different to malloc, in that `malloc` doesn't create an object. It merely gives you raw storage, without any type associated to it yet. However it doesn't make too much sense for me to argue about C++. I just need to quote the Standard. See 5.3.4 "The new-expression attempts to create an object of the type-id (8.1) or new-type-id to which it is applied. The type of that object is the allocated type.". This is actually different from C. C really hasn't zero length arrays (see how `malloc` and FAMs are specified in C - it tries hard not to say that a zero sized object is allocated). – Johannes Schaub - litb Nov 27 '10 at 17:22

6 Answers6

10

As arrays must have greater than zero length, if your compiler erroneously accepts a definition of a zero-sized array then you're "safely" outside of the scope of the language standard. There's no need for you to handle the edge case of N == 0.

This is true in C++: 8.3.5 [dcl.array]: If the constant-expression (5.19) is present, it shall be an integral constant expression and its value shall be greater than zero.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
3

Apparently ISO C forbids 0-length arrays, which is probably affecting how GCC tries to compile stuff. See this question for further details! zero length arrays vs. pointers

Community
  • 1
  • 1
Colen
  • 13,428
  • 21
  • 78
  • 107
2

The GCC Manual has a whole thing on zero length arrays. This is a GCC extension as is somewhat analogous to incomplete arrays.

doron
  • 27,972
  • 12
  • 65
  • 103
1

Speaking for C (and probably also C++ in this case), defining a zero-length array is undefined behavior, so GCC probably does this because a) nothing's stopping it, and b) it prevents errors like the ones you're trying to avoid.

Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
0

Zero-length arrays do not exist in C++. However if they did, here is how you could handle the case:

template <bool B, typename T>
struct disable_if;

template <typename T>
struct disable_if<false, T>
{
    typedef T type;
};

template <class T, size_t N>
typename disable_if<N == 0, void>::type foo(T(&c_array)[N])
{
    std::cout << "inside template\n";
}
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
-2

Zero sized array are only legal in C as the last element of atruct. Anything else is pointless.

engf-010
  • 3,980
  • 1
  • 14
  • 25