【问题标题】:For loop index type deduction best practicefor循环索引类型推演最佳实践
【发布时间】:2014-11-25 22:25:56
【问题描述】:

假设我有一个类型为c 的容器,它提供了一个size() 方法,我想在跟踪每个项目的索引的同时循环这个容器:

for (/*TODO*/ i = 0; i < c.size(); i++) {...}

在后 C++11 世界中,自动类型推导很好地解决了许多问题。我们应该用什么来代替上面的TODO?无论size() 是什么类型,我认为唯一正确的是以下内容:

for (decltype(c.size()) i = 0; i < c.size(); i++) {...}

但这似乎过于冗长,而且在我看来,这无助于可读性。

另一种解决方案可能是这样的:

for (auto end = c.size(), i = 0; i < end; i++) {...}

但这也无助于提高可读性,当然,与原始 sn-p 的语义也不相同。

所以,我的问题是:什么是推断循环索引变量类型的最佳方法,只给定索引限制的类型。

【问题讨论】:

  • 好吧,您可以使用foreach 循环并且根本不用理会计数器 :) 所有标准容器类型通常定义size_type
  • @user3159253:嗯,就我而言,我确实关心项目的索引 :-) 我也可能在处理非标准容器。
  • 目标是编写不管c 类型如何都能正常工作的通用(即模板化代码),还是您只是在寻找一种适用于所有情况的最佳实践?
  • 使用迭代器、std 算法和 lambda,您不必担心太多。
  • @ChrisDrew:这更像是一个最佳实践问题。

标签: c++ c++11 type-deduction


【解决方案1】:

对文本中第一个问题的简短回答:您应该将/*TODO*/ 替换为unsignedstd::size_t 或类似的东西,意思是:不要费心去推断类型,只需选择适合任何合理容器尺寸的类型。

这将是一个无符号的、相当大的类型,因此编译器不会因为可能的精度损失而对你大喊大叫。在上面的 cmets 中,您写道,size_t 不能保证成为decltype(c.size()) 的良好替代品,但是虽然实现索引与size_t 不兼容的容器并非不可能,这样的 indizes 肯定不是数字(因此与 i = 0 不兼容),并且容器也不会有 size 方法。 size() 方法意味着一个非负整数,并且由于 size_t 是为精确的这些数字而设计的,因此几乎不可能拥有一个无法用它表示的大小的容器。

您的第二个问题旨在如何推断类型,并且您已经提供了最简单但不完美的答案。如果您想要一个不像decltype 那样冗长且不像auto end 那样令人惊讶的解决方案,您可以在某些实用程序标头中为起始索引定义模板别名和生成器函数:

template <class T> 
using index_t = decltype(std::declval<T>().size());

template <class T, class U>
constexpr index_t<T> index(T&&, U u) { return u; }

//and then in the actual location of the loop:
for (auto i = index(c,0); i < c.size(); ++i) {...}
//which is the same as
for (auto i = index_t<std::vector<int>>(0); i < c.size(); ++i) {...}

如果您想要更通用的索引类型,例如对于没有size 方法的数组和类,它会变得有点复杂,因为模板别名可能不是专门的:

template <class T>
struct index_type {
  using type = decltype(std::declval<T>().size());
};

template <class T>
using index_t = typename index_type<T>::type;

template <class T, class U>
constexpr index_t<T> index(T&&, U u) { return u; }

//index_type specializations
template <class U, std::size_t N>
struct index_type<U[N]> { 
  using type = decltype(N); 
};

template <>
struct index_type<System::AnsiString::AnsiString> { //YUCK! VCL!
  using type = int; 
};

但是,这只是在少数情况下需要索引,而简单的 foreach 循环是不够的。

【讨论】:

  • 虽然我可能永远不会使用这种技术,但它是唯一能满足我要求的答案。如果没有其他问题,我会在几天内接受它。
  • 我同意这是一个有点麻烦的答案,我可能也永远不会使用它。我已经相应地编辑了我的答案。
  • 我同意对于任何 合理 容器,size_t 应该可以解决问题。但是,创建一个size()size_t 不兼容的容器肯定不是不可能。一个例子是 Qt 的容器,其中size() 返回一个int。此外,由于 size_t 只保证足够大以寻址地址空间中的每个字节,因此尝试寻址地址空间中每个 的容器可能具有与 @ 不兼容的 size() 987654343@.
  • 虽然 QT 的容器(和 VCL AnsiString::Length)返回 int,但它们仍然是非负 AFAIK,所以 size_t 应该没问题。 wrt 位寻址容器:如果您在索引语义非常不同的容器之间切换,那么整个循环语义会发生变化,手动更改索引类型将是该循环中最少的问题。无论如何,在您所说的其中一个 cmets 中,这是一个“最佳实践”问题,而漂亮的极端案例通常不会被最佳实践所涵盖;-)
  • 虽然它们确实返回非负的ints,但使用size_t 作为索引变量会产生编译器警告,提示比较具有不同符号的整数。这就是我所说的“与size_t 不兼容”。关于位寻址容器,我看不出vector&lt;bool&gt; 的索引语义与其他容器有何不同。无论如何,您对“最佳实践”的评论是完全正确的:-)
【解决方案2】:

如果 c 是一个容器,你可以使用container::size_type

【讨论】:

  • 如果它是“标准”容器类型,不幸的是,并非总是如此。
  • 如果c 不是一个“标准”容器,我会警惕编写通用代码并使用实际类型。
  • @ChrisDrew:我不太同意。我想独立于实际类型,因为它将来可能会改变。
  • @Job 你想对索引做什么?因为如果您不知道它是未签名的还是已签名的(例如QT vector,您需要非常小心地处理它(尤其是比较)。
  • @ChrisDrew:大多数情况下,您将使用它来访问容器的项目,例如,c.getItem(i),用于没有像样的迭代器的容器。但我还有很多其他用例。
【解决方案3】:

这是我遵循的优先级

1) range-for
2) iterator/begin()/end()auto 推断类型。

对于需要索引的情况,这是这里的主题,我更喜欢使用

for( auto i = 0u; i < c.size(); ++i) {...}

即使我没有在0 中添加u,编译器还是会警告我。


如果不是太冗长,我会喜欢decltype

for (decltype(c.size()) i = 0; i < c.size(); i++) {...}

【讨论】:

    【解决方案4】:

    嗯...这需要 C++14 或在 lambda 参数中支持 auto 的编译器。如果您经常使用这种模式,那么帮助功能可能会很有用:

    template< typename Container, typename Callable >
    void for_each_index( Container& container, Callable callable )
    {
        for (decltype(container.size()) i = 0; i < container.size(); i++)
        {
            callable(i);
        }
    }
    

    用作:

    for_each_index(c, [] (auto index) {
        // ...
    });
    

    【讨论】:

      【解决方案5】:

      事实上,我已经看到很多次(咳嗽 llvm,clang)他们使用的地方

      for (/* type */ iter = begin(), End = end(); iter != End; ++i);
      

      在开始时评估End 的好处是编译器可以确定它不需要每次都调用它。对于计算 end 很简单并且编译器已经能够推断出它不需要多次调用 end() 的集合,它没有帮助,但在其他情况下它会。

      或者你总是可以使用助手:

      Implementing enumerate_foreach based on Boost foreach

      【讨论】:

      • 是的,我知道这是经常使用的,我也知道它的优点。问题是,就我而言,它仅在end 出现在iter 之前才有效:for (auto end = ..., i = 0; ...)。我认为这有点令人困惑。
      • 编译器不会重新评估end(),如果它可以看到容器没有被修改。
      • @Abyx 不完全正确。如果它可以看到结果不会改变,它将不会重新评估end()。考虑一个容器,其 end() 方法的实现位于单独的源文件中。就编译器所知,end() 每次调用时都会返回一个随机值,因此它必须重新评估它。
      猜你喜欢
      • 2013-04-10
      • 2018-09-23
      • 2012-09-20
      • 1970-01-01
      • 2011-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多