【问题标题】:Looping on a closed range在封闭范围内循环
【发布时间】:2011-01-28 06:44:52
【问题描述】:

您将如何修复此代码?

template <typename T> void closed_range(T begin, T end)
{
    for (T i = begin; i <= end; ++i) {
        // do something
    }
}
  • T 被限制为整数类型,可以是更广泛的此类类型,并且可以是有符号或无符号的

  • begin可以是numeric_limits&lt;T&gt;::min()

  • end可以是numeric_limits&lt;T&gt;::max()(在这种情况下++i会在上面的代码中溢出)

我有好几种方法,但没有一个是我真正喜欢的。

【问题讨论】:

  • @ralu:问题的重点在于它是
  • 确实很有趣。我在减少循环时遇到了同样的问题 >> for (size_t i = 10; i &gt; 0; --i) 没有意义,因为 0size_t 可能达到的最小值(未签名)。
  • @Matthieu,size_t 是无符号的,你可以使用i--&gt;0,但是对于有符号的类型确实是同样的问题。
  • 你仍然会下溢(尽管在检查发生之后),因此必须希望没有硬件异常来捕获它并且它只是环绕......
  • @Matthieu,对于无符号类型,环绕是强制性的。

标签: c++ overflow numeric-limits


【解决方案1】:

也许,

template <typename T> void closed_range(T begin, const T end)
    if (begin <= end) {
        do {
            // do something
        } while (begin != end && (++begin, true));
    }
}

诅咒,我的第一次尝试是错误的,上面的修复并不像我希望的那样漂亮。怎么样:

template <typename T> bool advance(T &value) { ++value; return true; }

template <typename T> void closed_range(T first, const T last)
    if (first <= last) {
        do {
            // do something
        } while (first != last && advance(first));
    }
}

即使 T 不是整数类型,std::advance 也没有歧义,因为std::advance 有 2 个参数。因此,如果出于某种原因您想要一个封闭的范围,该模板也可以与随机访问迭代器一起使用。

或者来点集合论怎么样?显然,如果您只在一个封闭范围内编写一个循环,这将是巨大的矫枉过正,但如果这是您想做很多事情的事情,那么它会使循环代码大致正确。不确定效率:在一个非常紧凑的循环中,您可能需要确保调用endof

#include <limits>
#include <iostream>

template <typename T>
struct omega {
    T val;
    bool isInfinite;
    operator T() { return val; }
    explicit omega(const T &v) : val(v), isInfinite(false) { }
    omega &operator++() {
        (val == std::numeric_limits<T>::max()) ? isInfinite = true : ++val;
        return *this;
    }
};

template <typename T>
bool operator==(const omega<T> &lhs, const omega<T> &rhs) {
    if (lhs.isInfinite) return rhs.isInfinite;
    return (!rhs.isInfinite) && lhs.val == rhs.val;
}
template <typename T>
bool operator!=(const omega<T> &lhs, const omega<T> &rhs) {
    return !(lhs == rhs);
}

template <typename T>
omega<T> endof(T val) { 
    omega<T> e(val);
    return ++e;
}

template <typename T>
void closed_range(T first, T last) {
    for (omega<T> i(first); i != endof(last); ++i) {
        // do something
        std::cout << i << "\n";
    }
}

int main() {
    closed_range((short)32765, std::numeric_limits<short>::max());
    closed_range((unsigned short)65533, std::numeric_limits<unsigned short>::max());
    closed_range(1, 0);
}

输出:

32765
32766
32767
65533
65534
65535

omega&lt;T&gt; 对象上使用其他运算符时要小心。我只实现了演示的绝对最小值,omega&lt;T&gt; 隐式转换为T,因此您会发现您可以编写可能丢弃 omega 对象的“无限性”的表达式。您可以通过声明(不一定定义)一整套算术运算符来解决这个问题;或者如果 isInfinite 为真,则在转换中抛出异常;或者只是不要担心它,因为你不能不小心将结果转换回欧米茄,因为构造函数是显式的。但是比如omega&lt;int&gt;(2) &lt; endof(2)为真,而omega&lt;int&gt;(INT_MAX) &lt; endof(INT_MAX)为假。

【讨论】:

    【解决方案2】:

    编辑:重新设计以更接近 OP。

    #include <iostream>
    using namespace std;
    
    template<typename T> void closed_range(T begin, T end)
    {
        for( bool cont = (begin <= end); cont; )
        {
            // do something
            cout << begin << ", ";
            if( begin == end )
                cont = false;
            else
                ++begin;
        }
    
        // test - this should return the last element
        cout << " --  " << begin;
    }
    int main()
    {
        closed_range(10, 20);
        return 0;
    }
    

    输出是:

    10、11、12、13、14、15、16、17、18、 19, 20, -- 20

    【讨论】:

      【解决方案3】:
      template <typename T, typename F>
      void closed_range(T begin, T end, F functionToPerform) 
      {
          for (T i = begin; i != end; ++i) { 
              functionToPerform(i);
          }
          functionToPerform(end);
      } 
      

      【讨论】:

      • 你可以在这里看到它的实际效果:codepad.org/4zctR9w5(从 Kirill 借来的测试工具)
      • 真的,你不需要证明它有效。这种半开迭代风格在 C++ 中是惯用的,因此与更复杂的示例相比,混淆的空间要小得多。
      • @Dennis:很高兴听到这个消息。这主要是玩键盘的借口,我最近才发现。 :)
      • 这很好,前提是您不需要让封闭范围为空的方法。更复杂的示例允许额外的极端情况。
      • @Steve:正如所写,closed_range 接受任何 forward_iterator,就像标准算法一样。如果您想将输入限制为仅 random_access_iterator,那么您可以测试 if (begin &gt; end)。 (因为标准函数只接受 input_iterators,如果你用 begin &gt; end 调用它们,它会产生不良行为。)如果 closed_range 的调用者知道他们有一个 random_access_iterator 他们可以自己做检查,就像他们必须做的那样任何其他算法。
      【解决方案4】:

      这行得通,而且相当清楚:

      T i = begin;
      do {
         ...
      }
      while (i++ < end);
      

      如果您想捕捉 begin &gt;= end 的特殊情况,您需要像 Steve Jessop 的解决方案一样添加另一个 if

      【讨论】:

      • +1:很好的干净解决方案,此外,世界上还缺少 do-while 循环。
      • 这个问题是在TintendINT_MAX的情况下,当i的值是INT_MAX时它增加i。这是未定义的行为,即使 i 的值不再使用。
      • 公平地说,我应该补充一点,与提问者的代码在 all 实现上出错的问题相比,这是一个相当小的问题 ;-)
      • @Steve:如果 i=INT_MAX,在这种情况下,即使“i”的值在操作完成后未定义,i++ 的返回值是否定义为 INT_MAX?我问,因为我不知道。 :)
      • 一般来说,一旦你引入了未定义的行为,所有的赌注都没有了。例如,您的系统可能会在整数溢出时引发异常。
      【解决方案5】:

      我的看法:

      // Make sure we have at least one iteration
      if (begin <= end)
      {
          for (T i = begin; ; ++i)
          {
              // do something
      
              // Check at the end *before* incrementing so this won't
              // be affected by overflow
              if (i == end)
                  break;
          }
      }
      

      【讨论】:

      • 我觉得和assign; while(test) {increment; code}一样干净一点
      • @Jefromi:分配什么?你需要T i = begin - 1;,它在另一个方向上仍然存在溢出问题。
      • 这是有争议的。在这种情况下,while 强调中断条件,for 强调迭代。任何一种方法都需要您搜索等式的另一半[顺便说一下,您希望增量位于循环的末尾,否则您将更改循环的语义]。
      猜你喜欢
      • 2017-08-05
      • 1970-01-01
      • 2013-11-01
      • 2013-04-14
      • 2019-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多