【问题标题】:Can a `constexpr std::initializer_list` be used at compile time but not ODR-used?可以在编译时使用“constexpr std::initializer_list”但不能使用 ODR 吗?
【发布时间】:2016-08-10 18:28:36
【问题描述】:

在以下独立程序中,我预计 for 循环会在编译时展开,甚至完全计算,从而使 Foo::MyNumbers 在链接时不必要:

struct Foo
{
  constexpr static auto MyNumbers =
  {
    3,
    28,
    200,
  };
};

constexpr int getSum(void)
{
  auto sum = 0;
  for (constexpr auto i : Foo::MyNumbers)
  {
    sum += i;
  }
  return sum;
}

但是,即使使用-O3clang++ 3.7(从源代码之前构建到 3.7 版本)和g++ 5.1 都会给出类似的错误; clang 表示read of non-constexpr variable '__begin' is not allowed in a constant expression,而g++ 表示the value of '__for_begin' is not usable in a constant expression

我真的想不出任何可以在编译时使用std::initizlizer_list 而不迭代它,实际上是the begin() method is indeed marked constexpr as of C++14 来有用地完成的事情。那么有什么可以通过constexpr std::initializer_list 有效地完成而不需要定义 ODR 的吗?

注意:我在两个编译器上都使用-std=c++14 进行编译,但我意识到它们可能并不完全一致——即使我使用的版本是相当 最近。我很想知道是否有更多最新版本允许上面的代码。

编辑:在与 ChrisBeck 讨论后更改了我的示例和分析;请参阅他的回答下的讨论,以及此问题的编辑历史记录。

编辑 2: 在 T.C. 的建议下,我从 for 循环中删除了 constexpr,留下了 for (auto i : Foo::MyNumbers)。这会导致 GCC 和 Clang 出现 undefined reference to 'Foo::MyNumbers' 链接错误。

【问题讨论】:

  • 您的新示例与 odr-use 无关,与基于范围的 for 规范有关。
  • @T.C.哎呀。如果您指的是我在constexpr 函数中使用std::cout,那是草率编辑的产物。我已经纠正了。据我所知,从 C++14 开始,getSum 中的所有内容都应该在 constexpr 函数中是允许的。
  • 不。你正在做相当于auto __begin = Foo::MyNumbers.begin(); constexpr auto i = *__begin;
  • @T.C. ....或者您是说在constexpr 上下文中从不允许基于范围的for
  • IIRC,如果不使用 ODR,您将无法访问数组的元素,initializer_list 基于数组。但是,您可以对这些地址进行指针运算,例如Foo::MyNumbers.end() - Foo::MyNumbers.begin()。 clang++ 拒绝在编译时读取initializer_list 的元素,请参阅melpon.org/wandbox/permlink/InGwn1kW94rcuXh0 -- gcc 可以这样做。

标签: c++ c++11 constexpr one-definition-rule


【解决方案1】:

ODR 不依赖于优化选项。 ODR 是标准的一部分。

该标准未提及优化选项。相反,不同的编译器应该按照他们认为合适的方式组成不同的优化方案,并且在这方面有很大的余地。 ODR 应该确保符合标准的代码将链接到所有符合标准的编译器上。 (感谢 C++ 标准委员会!)

所以,不要再考虑循环展开以及链接时的含义了。对 ODR 而言,唯一重要的是某事物是否被 ODR 使用。

我真的想不出任何可以在编译时有用地完成的事情......除了循环展开和初始化对象

由于 C++14 std::initializer_list 是文字类型。因此,您可以轻松地在编译时计算中使用它。使用std::initializer_list 初始化具有 constexpr 构造函数的对象在某些代码库中非常常见。

【讨论】:

  • 对不起,但这感觉像是没有答案。提到-O3 是因为如果没有优化,循环展开可能不会发生,如果没有循环展开,for 循环将需要一个实际的迭代器和相关的运行时包袱,这需要std::initializer_list 存在在运行时——因此它将被 ODR 使用,因此它必须是 ODR 定义的。 (是的,我相信种情况下,Clang/GCC 会编译具有高优化级别的代码,但没有优化就会失败)。
  • 当您说“您可以轻松地在编译时计算中使用它”时,您是在查看一个示例,尝试在编译时计算中使用它失败了。我仍然不明白它是如何使用的。请注意,从 C++14 开始,std::initializer_list::begin() 标记为constexpr
  • 好的,让我这样说——循环展开,如果发生,发生在检查 odr 之后。在 C++ 中,必须满足 ODR,而不假设某些有问题的用法将通过优化消除。实际上,如果您阅读该标准,您会发现单一定义规则的文本没有提及循环展开、优化、死代码消除或任何其他内容。
  • 如果您仍然觉得这是一个无法回答的问题,您可以做的一件事是阅读标准,查看您的代码,尝试争论为什么 Foo::MyNumbers 不使用 ODR,然后发布标有“language-lawyer”标签的第二个问题,关于为什么编译器似乎认为它是 ODR 使用的。
  • 至于constexpr std::initializer_list如何使用,请尝试在constexpr函数中使用。 int main 不是这样。这与您的问题“为什么我的代码没有链接”确实是一个不同的问题。
【解决方案2】:
for (constexpr auto i : Foo::MyNumbers)
{
  sum += i;
}

无论包含它的函数是否为constexpr,这都是无效的,因为它扩展为

{
    auto&& __range = Foo::MyNumbers;
    for(auto __begin = __range.begin(), __end = __range.end();
        __begin != __end;
        ++__begin) {
        constexpr auto i = *__begin;
        sum += i;
    }
}

*__begin 显然不是一个常量表达式(它需要对非constexpr 变量__begin 进行左值到右值的转换),因此不能用于初始化constexpr 变量i .

【讨论】:

  • 嗯,很有趣。在那种情况下,为什么begin() 甚至被标记为constexpr?它实际上可以在constexpr 上下文中有用吗?
  • @KyleStrand 因为您可以在constexpr 函数中使用该循环,如果您将constexpr 放在i 上就可以了吗?
  • 我想我不明白为什么 i 是否被声明为 constexpr 很重要,但实际上即使删除 constexpr 也不能解决问题;然后我回到undefined reference to 'Foo::MyNumbers'链接错误。
猜你喜欢
  • 2017-08-11
  • 2012-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-16
  • 1970-01-01
  • 2017-10-12
  • 1970-01-01
相关资源
最近更新 更多