基于范围的 for 数组 ODR 循环是否使用该数组?

Tre*_*ntP 6 c++ one-definition-rule c++14

当使用基于范围的 for 循环来迭代数组而不将引用绑定到每个元素时,这是否构成数组的 ODR 使用?

例子:

struct foo {
    static constexpr int xs[] = { 1, 2, 3 };
};

int test(void) {
    int sum = 0;
    for (int x : foo::xs) // x is not a reference!
        sum += x;
    return sum;
}

// Definition, if needed
///constexpr foo::xs;
Run Code Online (Sandbox Code Playgroud)

的定义有foo::xs必要吗?

虽然此代码及其变体似乎运行良好,但这并不意味着该定义永远不需要。缺乏 ODR 使用变量的定义很少会产生诊断,因为该变量可以在另一个翻译单元中定义。链接器错误是常见的结果,但如果编译器能够优化每次使用,则很可能不会出现错误,这就是上述代码所发生的情况。编译器有效地简化test()return 6;.

将引用绑定到元素将是 ODR 使用,但这还没有完成。

我的印象是,在 C++14 或更高版本中,为数组添加下标不是 ODR 使用。但基于的范围并不完全是下标。

在 C++17 中,我相信这个示例避免了这个问题,因为 constexpr 类数据成员是隐式内联的。因此,类中的声明也用于定义xs,并且不需要额外的命名空间范围定义来满足 ODR。

同一问题的一些附加版本:

如果我们使用std::array呢?

constexpr std::array<int, 3> xs = { 1, 2, 3 };
Run Code Online (Sandbox Code Playgroud)

如果我们避开基于的范围怎么办?

for (int i = 0; i < foo::xs.size(); i++) sum += foo::xs[i];
Run Code Online (Sandbox Code Playgroud)

Bri*_*ian 2

的定义有foo::xs必要吗?

是的,因为正如 NathanOliver 在评论中指出的那样,引用是foo::xs通过基于范围的 for 循环隐式绑定的。当您将引用绑定到对象时,该对象将被 odr 使用。std::array如果使用an 而不是原始数组,也会发生同样的情况。

如果我们避开基于的范围怎么办?

好吧,如果您使用原始数组并使用不需要绑定引用的技术来获取其大小,那么您可以避免提供定义:

for (int i = 0; i < sizeof(foo::xs)/sizeof(foo::xs[0]); i++) {
    sum += foo::xs[i];
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,内部的引用sizeof不是 odr 用途,因为它们未经评估,并且foo::xs是 ; 的潜在结果集的元素foo::xs[i]。后一个表达式是非类类型,并且立即进行左值到右值的转换,因此它不会 odr-use foo::xs