C++中的模板无法推断零长度数组的大小

6
假设我有一个模板函数,它可以推断出数组参数的长度。
template <size_t S>
void join(const char d[], const char *(&arr)[S]) { }

如果我像这样调用,一切都很好:
const char *messages[] = {
    "OK",
    "Not OK",
    "File not found"
};
join("\n", messages);

但如果我使用空数组调用它,像这样:

const char *messages[] = { };
join("\n", messages);

...它不能编译(使用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.

我猜这与C++不喜欢零长度数组有关,但如果该函数不是模板并将长度作为单独参数传递,则它不会抱怨我声明消息为零长度数组。

这里出了什么问题?是否有一个好的解决方法?


我的实际用例是定义HTTP API端点接受的参数,看起来像这样:

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);

大多数端点至少需要一个参数,但许多端点不需要参数。看起来这确实是GCC和clang都实现的扩展(但似乎并不完全……)。我可以想到几个解决方法:
  • 重载api_endpoint构造函数以特殊处理零长度参数(但我需要23个来覆盖每个可零长度参数),这是GCC / clang扩展可以接受的。

  • 不要尝试推断数组长度,将其作为单独的参数(并继续使用零长度数组)

  • 对于这些参数,使用更高级别的数据结构,如向量

  • 使用魔术值表示“空”

...但如果有更好的想法,我很乐意听取。


最好直接使用 std::vector - Puppy
std::arrayboost::array 也支持长度为零。 - Johannes Schaub - litb
@JohannesSchaub-litb 我喜欢那个想法,但是在 C++11 之前无法使用字面量(比如 这个问题)。使用更高级的数据结构会导致代码更冗长(特别是因为我需要为每个项目使用带有类型名称的复合对象)。 - s4y
1个回答

8

这段代码本来就是非法的:

const char *messages[] = { };

这是我的编译器产生的错误和警告:

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.

所以零长度数组实际上根本不被允许。您的编译器似乎有一个零长度数组的扩展,但是它并没有涵盖这种特定情况。有时候扩展就是这样,因为扩展需要更少的工作来使其与整个语言保持一致。
解决方法将取决于您为什么需要零长度数组以及您在其他地方如何使用它。一种解决方法可能是使用单个元素数组。
以下是一个解决方法。由于该扩展不允许推断数组大小为零,请添加一个不需要此推断的重载:
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);
}

请注意,这里使用的是扩展功能而不是可移植代码。为了避免受到特定C++实现的限制,您可能更喜欢编写可移植代码。您可以通过在构建过程中添加-Wzero-length-array标志来查看您依赖该扩展功能的程度。


啊,谢谢!我和clang的人核实了一下,他们确认这是一个扩展。真糟糕,我们在几个地方都在使用它们,这让代码变得简洁而美观。 - s4y
也许如果您提供一个使用示例,有人可能会建议一个不错的替代方案。 - bames53
当然,我刚刚在问题中添加了一个。 - s4y

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接