注文したコンテナのサブシーケンスを扱うライブラリを書いています。
たとえば、コンテナ(1,2,3,4,5,6)があり、ユーザーがアクセスしたい(3,4,5)とします。
最初の要素とlast要素をそれぞれ指す、つまり3と5のイテレータのペアによってサブシーケンスを提供しています。
ライブラリはC++とAFAIKで記述されているため、stdの規則では最後のイテレータポイントbeyondが最後の要素になるようになっています。イテレータ、最初と最後を超えて要素をそれぞれ指す、つまり3と6?
また、プログラミングの観点からは、std機能を使用する場合、たとえば要素の数を数えるために物事を複雑にします。
int elementCnt = std::distance(startIt, endIt) + 1;
標準に従ってください-終わりはあなたが望むものを過ぎたイテレータです。これにより、すべての標準アルゴリズムとコンテナを問題なく使用できます。
また、ユーザーが常に持っているコードを記述できるようになります(例:for (x=startIt; x != endIt; x++)
)。これは期待どおりに機能します。
この動作を変更して、最後のイテレーターを最後の要素に設定すると、すべてがウィンドウの外に出て、イテレーターとは異なる命名法を使用して、全員が期待する動作を効果的に変更することができます。
あなたの慣習で:
半分閉じた範囲を維持する必要があります。
あなたが書いた:
Stdの規則では、最後の要素を超えて最後の反復子ポイントを使用します
私はあなたに2つの小さな返信(それぞれ1つのセクション)を与えることによってあなたのメンタルモデルを助けることができると思います。
Snowman による非常に役立つコメントのおかげで、このセクションを大幅に簡略化してC++化しました。
C++イテレータは、「現在どの項目を指しているか」ではなく、「次に取得する項目」という用語で定義されています。
そのため、イテレータをアイテムではなくエッジの直前に置くと考えると役立ちます。
開始と停止のあるサブシーケンスの場合、アイテムに番号を付ける代わりに、アイテム間のエッジに精神的に番号を付けます。 0は最初のアイテムの前のエッジです。 startIt
は私が始めたエッジです。 stopIt
は、私が立ち寄るエッジです。
次の図は Pythonの非公式な紹介 からの抜粋です。
item 0 1 2 3 4 5
+---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
iterator 0 1 2 3 4 5 6
したがって、startIt = 2
およびstopIt = 5
はt, h, o
につながります。
あなたはいくつかの本当に素晴らしいプロパティを得ます:
n = stop - start
stop of one == start of the next.
以下の例。 Python構文は以下の構文を使用しています。C++がわからないためです。誰かがこのセクションをC++に翻訳したいと思っている場合は(Pythonを離れる必要はありません)、非常に感謝します。)とにかく、表記は重要ではありません。[start:stop]
をstartIt
とstopIt
として読んでください。
これが使用するコンテナです
my_container = [ 'a', 'b', 'c', 'd' ]
## edges ^ ^ ^ ^ ^
## 0 1 2 3 4
c[start:stop]
のようにスライスしてサブシーケンスにアクセスします-エッジ1と3の間のすべてを取得します。
my_container[1:3] == ['b', 'c']
長さ3のスライスを取得するには、stop=startを確認します+ 3
my_container[1:4] == ['b', 'c', 'd']
# or do stop - start to find out how long the slice is:
4 - 1 == 3 # 3 elements in this slice.
前のスライスが終わるところから1つのスライスを始めたい。つまり、最初のスライスはEdgexで終わり、2番目のスライスはEdgex。この方法で、コンテナーをきれいに2つに分割します。
my_container[0:3] == ['a', 'b', 'c']
my_container[3:4] == ['d']
manlio's answer のEdsger W. Dijkstraによるエッセイを読んでください。 700ワード未満で、非常に明確な思考と同様に明確な手書き(および内部のHTMLバージョンへのリンク)を備えています。
標準ライブラリは、正当な理由で、過去の終わりのポインタを使用します-このパターンに固執します。別の方法では、空の範囲を説明する良い方法はありません。
また、何か特別な/特別な/ ここに書かれたが絶対に必要でない限り、単に Boost.Range を使用してください。
プログラミング言語で使用されている規則(定義)を維持する必要があります。
例:
#include <iterator>
template< class Iterator>
class Range
{
public:
typedef typename std::iterator_traits<Iterator>::value_type value_type;
typedef Iterator iterator;
Range(const iterator& first, const iterator& last) noexcept
: m_first(first), m_last(last)
{}
Range(iterator&& first, iterator&& last) noexcept
: m_first(std::move(first)), m_last(std::move(last))
{}
Range(Range&& other) noexcept
: m_first(std::move(other.m_first)),
m_last(std::move(other.m_last))
{}
Range& operator = (Range&& other) noexcept {
m_first = std::move(other.m_first);
m_last = std::move(other.m_last);
return *this;
}
iterator begin() const noexcept { return m_first; }
iterator end() const noexcept { return m_last; }
private:
iterator m_first;
iterator m_last;
};
template<typename T>
inline Range<T> range(T&& first, T&& last) noexcept {
return Range<T>(std::forward<T>(first), std::forward<T>(last));
}
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = { 1,2,3,4,5,6 };
for(auto i : range(v.begin() + 1, v.end() - 1))
std::cout << i << '\n';
for(auto i : range(v.end(), v.end()))
std::cout << i << '\n';
}
慣習に固執しないと、ライブラリ(アルゴリズム)や言語機能(範囲ベース)の使用が面倒になります。さらに悪いことに、一般的な慣習を期待しているプログラマが微妙なエラーにつながる可能性があります。また、[first、last]が含まれている場合、空の範囲を表す方法はありません。