Let’s say I have a template function that infers the length of an array parameter.
template <size_t S>
void join(const char d[], const char *(&arr)[S]) { }
If I call it like this, all is well:
const char *messages[] = {
"OK",
"Not OK",
"File not found"
};
join("\n", messages);
But if I call it with an empty array, like this:
const char *messages[] = { };
join("\n", messages);
…it doesn’t compile (with clang 4.0):
targs.cpp:9:5: error: no matching function for call to 'join' join("\n", messages); ^~~~ targs.cpp:4:6: note: candidate template ignored: substitution failure [with S = 0] void join(const char d[], const char *(&arr)[S]) { } ^ 1 error generated.
I’m guessing that it has something to do with C++ not liking zero-length arrays, but if the function is not a template and takes the length as a separate parameter, it doesn’t complain about me declaring messages as a zero-length array.
What’s up here, and is there a nice workaround?
My actual use case is defining the parameters an HTTP API endpoint takes and looks something like this:
const api_param_t params[] = {
{ API_TYPE_STRING, "foo" },
{ API_TYPE_UINT64, "bar" },
{ API_TYPE_STRING, "baz" }
}
const api_status_t status_codes[] = { … };
const api_middleware_t middleware[] = { … };
new api_endpoint("/foo", params, status_codes, middleware);
Most endpoints take at least one parameter but many take none. It looks like this is, indeed, an extension which both GCC and clang implement (but, looks like, not completely…). I can think of a few workarounds:
Overload the api_endpoint
constructor to special case zero-length arguments (but I need 23 of them to cover each zero-length-able parameter), which the GCC/clang extension is OK with.
Don’t try to infer the array length, take it as a separate parameter (and continue to use zero-length arrays)
Use a higher-level data structure like a vector for these parameters
Use a magic value to indicate "empty"
…but if anyone has better ideas I’d love to hear ‘em
This code isn't legal in the first place:
const char *messages[] = { };
Here are the errors and warnings my compiler produces:
main.cpp:6:26: warning: zero size arrays are an extension [-Wzero-length-array]
const char *messages[] = { };
^
main.cpp:7:1: error: no matching function for call to 'join'
join("\n", messages);
^~~~
main:3:6: note: candidate template ignored: substitution failure [with S = 0]: zero-length arrays are not permitted in C++
void join(const char d[], const char *(&arr)[S]) { }
^ ~
1 warning and 1 error generated.
So zero length arrays aren't actually allowed at all. Your compiler appears to have an extension for zero length arrays which, however, does not cover this specific case. Extensions are like that sometimes, because less work goes into extensions to make them work consistently with the whole language.
A workaround will depend on why you want a zero length array and how you're using it elsewhere. One workaround might be using a single element array instead.
Here's a work around. Since the extension does not allow array sizes to be deduced as zero add an overload that does not require this deduction:
template <size_t S>
void join(const char d[], const char *(&arr)[S]) {
std::cout << "array length > 0\n";
}
void join(const char d[], const char *(&arr)[0]) {
std::cout << "extension, zero length array\n";
}
int main() {
const char *messages[] = {
"OK",
"Not OK",
"File not found"
};
join("\n", messages);
const char *messages2[] = { };
join("\n", messages2);
}
You should keep in mind that this is using an extension and is not portable code. You may prefer to write portable code in order to avoid being locked into any particular C++ implementation. You can see how much you rely on this extension by adding the flag -Wzero-length-array
to your builds.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With