【问题标题】:What is clang's 'range-loop-analysis' diagnostic about?clang 的“范围循环分析”诊断是什么?
【发布时间】:2023-03-28 13:45:01
【问题描述】:

背景:

考虑以下example

#include <iostream>
#include <vector>

int main() {
    std::vector<bool> vectorBool{false, true};
    for(const auto &element : vectorBool) std::cout << std::boolalpha << element << ' ';
    return 0;
}

它发出警告:

test.cpp:6:21: warning: loop variable 'element' is always a copy because the range of type 'std::vector<bool>' does not return a reference [-Wrange-loop-analysis]
    for(const auto &element : vectorBool) std::cout << std::boolalpha << element << ' ';
                    ^
test.cpp:6:9: note: use non-reference type 'std::_Bit_reference'
    for(const auto &element : vectorBool) std::cout << std::boolalpha << element << ' ';
        ^~~~~~~~~~~~~~~~~~~~~
1 warning generated.

使用 clang 编译并启用range-loop-analysis 诊断时:

$ clang++ -Wrange-loop-analysis -o test test.cpp

问题:

根据https://reviews.llvm.org/D4169,警告在以下时间发出:

for (const Foo &x : Foos),其中 Foos 范围仅返回一个副本。建议使用非引用类型,这样文案就很明显了

我完全理解std::vector&lt;bool&gt; 的迭代器返回代理类型的副本(而不是引用),但我不同意“所以副本显而易见”的说法:

  1. “隐藏”复制操作到底发生在哪里?据我所知,我们只是将引用绑定到一个临时对象,这应该会延长临时对象的生命周期以匹配参考。
  2. 即使我们已经编写了for(const auto element : vectorBool)(这样警告就会消失),我们在C++17's guaranteed copy elision rules 下也不应该有复制/移动操作(甚至在使用任何体面的编译器时在C++ 17 之前),所以关于使 省略 复制操作明显的警告吗?!

【问题讨论】:

  • 乍一看,对于您发布的代码,这个警告对我来说看起来很愚蠢/错误。
  • @JesperJuhl 不幸的是,代理类型收到了这个警告,但我怀疑分析器能否区分代理和一些奇怪的东西。如果一切都失败了,责备std::vector&lt;bool&gt;
  • @Passer By - vector&lt;bool&gt;,是的,我希望它从未被创建过..
  • @Mike 我也不希望看到这种情况发生。分析只看类型,推断返回值不可能是容器中的元素,something一定是被复制了。然后它建议写auto : Foos 以明确这一事实。在这种情况下,代理在概念上没有复制任何东西,但我怀疑分析器可以对此做任何事情。它试图警告人们注意奇怪的非代理案例。
  • @Mike 考虑某种范围适配器,例如uppercase(some_vector_of_strings)。它不可能返回对容器中对象的引用,因为它必须在返回之前修改每个元素。

标签: c++ clang compiler-warnings clang++ copy-elision


【解决方案1】:

在 C++17 中,基于范围的 for 循环定义为

{
    auto && __range = range_expression ; 
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) { 
        range_declaration = *__begin; 
        loop_statement 
    } 
}

range_declaration = *__begin;

是初始化范围变量的点。通常*__begin 返回一个引用,所以在

for (const auto& e : range_that_returns_references)

e 可以被消除,我们可以只使用范围中的元素。在

for (const auto& e : range_that_returns_proxies_or_copies)

e 不能被淘汰。 *__begin 将创建一个代理或副本,然后我们将该临时绑定到 e。这意味着在每次迭代中,您都有一个正在创建和销毁的对象,这可能代价高昂,并且在使用引用时并不明显。该警告希望您使用非引用类型,以表明您实际上并未使用范围中的元素,而是使用它的副本/代理。

【讨论】:

  • 我明白了,所以警告只是为了确保用户了解正在创建一个临时对象(而不仅仅是传递引用)。但是,在我看来,当用户使用for(auto &amp;&amp; e : vectorBool) 时不应发出警告,因为使用转发引用足以表明用户愿意接受任何类型的返回类型...
  • @Mike 我认为这是合理的。我不会想出任何不明显的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-29
  • 2013-02-11
  • 1970-01-01
  • 2011-03-26
  • 1970-01-01
相关资源
最近更新 更多