web-dev-qa-db-ja.com

「範囲ベースのforループ」で動作するようにカスタムタイプを作成する方法は?

最近の多くの人々のように、C + 11がもたらすさまざまな機能を試してきました。私のお気に入りの1つは、「範囲ベースのforループ」です。

という事は承知しています:

for(Type& v : a) { ... }

以下と同等です:

for(auto iv = begin(a); iv != end(a); ++iv)
{
  Type& v = *iv;
  ...
}

そして、そのbegin()は、単に標準コンテナに対してa.begin()を返します。

しかし、カスタムタイプを「範囲ベースのforループ」対応にするにしたい場合はどうでしょうか。

begin()end()を専門にすべきですか?

カスタムタイプが名前空間xmlに属している場合、xml::begin()またはstd::begin()を定義する必要がありますか?

要するに、それを行うためのガイドラインは何ですか?

226
ereOn

標準の関連部分は6.5.4/1です。

_RangeTがクラス型の場合、クラスメンバーアクセスルックアップ(3.4.5)のように、クラス_RangeTのスコープで未修飾IDの始まりと終わりが検索され、どちらか(または両方)が少なくとも1つの宣言を見つけたら、始まります-exprおよびend-exprは、それぞれ__range.begin()および__range.end()です。

—それ以外の場合、begin-exprとend-exprはそれぞれbegin(__range)end(__range)で、beginとendは引数依存のルックアップで検索されます(3.4.2)。この名前ルックアップのために、名前空間stdは関連付けられた名前空間です。

そのため、次のいずれかを実行できます。

  • beginおよびendメンバー関数を定義する
  • aDLによって検出されるbeginおよびend free関数を定義します(簡易バージョン:クラスと同じネームスペースに配置します)
  • std::beginおよびstd::endを専門にする

とにかくstd::beginbegin()メンバー関数を呼び出します。したがって、上記のいずれかのみを実装する場合、どちらを選択しても結果は同じになります。これは、範囲ベースのforループでも同じ結果であり、独自の名前解決規則を持たない単なる致命的なコードでも同じ結果になります。したがって、using std::begin;の後にbegin(a)の非修飾呼び出しが行われます。

ただし、メンバー関数and ADL関数を実装する場合、範囲ベースのforループはメンバー関数を呼び出す必要がありますが、単なる人間はADL関数を呼び出します。その場合は、必ず同じことを行うようにしてください!

あなたが書いているものがコンテナインターフェースを実装している場合、それはすでにbegin()およびend()メンバー関数を持っています。これは十分なはずです。コンテナではない範囲の場合(不変の場合、またはサイズが事前にわからない場合は良い考えです)、自由に選択できます。

レイアウトするオプションのうち、必須ではないstd::begin()をオーバーロードすることに注意してください。ユーザー定義型の標準テンプレートを特化することは許可されていますが、それ以外に、名前空間stdに定義を追加することは未定義の動作です。しかし、とにかく、標準関数の特殊化は、部分的な関数の特殊化がないために、クラステンプレートではなく単一のクラスに対してのみ行うことができるため、適切な選択ではありません。

52
Steve Jessop

一部の人々は、STLが含まれていない単純な実生活の例により満足しているかもしれないので、私は私の答えを書きます。

なんらかの理由で独自のプレーンのみのデータ配列の実装があり、範囲ベースのforループを使用したいと考えました。私の解決策は次のとおりです。

 template <typename DataType>
 class PodArray {
 public:
   class iterator {
   public:
     iterator(DataType * ptr): ptr(ptr){}
     iterator operator++() { ++ptr; return *this; }
     bool operator!=(const iterator & other) const { return ptr != other.ptr; }
     const DataType& operator*() const { return *ptr; }
   private:
     DataType* ptr;
   };
 private:
   unsigned len;
   DataType *val;
 public:
   iterator begin() const { return iterator(val); }
   iterator end() const { return iterator(val + len); }

   // rest of the container definition not related to the question ...
 };

次に、使用例:

PodArray<char> array;
// fill up array in some way
for(auto& c : array)
  printf("char: %c\n", c);
40
csjpeter

Begin()とend()を専門にすべきですか?

私の知る限り、それで十分です。また、ポインタの増分が開始から終了までになることを確認する必要があります。

次の例(beginおよびendのconstバージョンがありません)がコンパイルされ、正常に動作します。

#include <iostream>
#include <algorithm>

int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }
    int * begin()
    {
        return &v[0];
    }
    int * end()
    {
        return &v[10];
    }

    int v[10];
};

int main()
{
    A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}

関数としてbegin/endを使用する別の例を次に示します。それらはADLのために、クラスと同じ名前空間にある必要があります

#include <iostream>
#include <algorithm>


namespace foo{
int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }

    int v[10];
};

int *begin( A &v )
{
    return &v.v[0];
}
int *end( A &v )
{
    return &v.v[10];
}
} // namespace foo

int main()
{
    foo::A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}
32
BЈовић

クラスの反復をそのstd::vectorまたはstd::mapメンバーで直接バックアップする場合、そのためのコードは次のとおりです。

#include <iostream>
using std::cout;
using std::endl;
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <map>
using std::map;


/////////////////////////////////////////////////////
/// classes
/////////////////////////////////////////////////////

class VectorValues {
private:
    vector<int> v = vector<int>(10);

public:
    vector<int>::iterator begin(){
        return v.begin();
    }
    vector<int>::iterator end(){
        return v.end();
    }
    vector<int>::const_iterator begin() const {
        return v.begin();
    }
    vector<int>::const_iterator end() const {
        return v.end();
    }
};

class MapValues {
private:
    map<string,int> v;

public:
    map<string,int>::iterator begin(){
        return v.begin();
    }
    map<string,int>::iterator end(){
        return v.end();
    }
    map<string,int>::const_iterator begin() const {
        return v.begin();
    }
    map<string,int>::const_iterator end() const {
        return v.end();
    }

    const int& operator[](string key) const {
        return v.at(key);
    }
    int& operator[](string key) {
        return v[key];
    } 
};


/////////////////////////////////////////////////////
/// main
/////////////////////////////////////////////////////

int main() {
    // VectorValues
    VectorValues items;
    int i = 0;
    for(int& item : items) {
        item = i;
        i++;
    }
    for(int& item : items)
        cout << item << " ";
    cout << endl << endl;

    // MapValues
    MapValues m;
    m["a"] = 1;
    m["b"] = 2;
    m["c"] = 3;
    for(auto pair: m)
        cout << pair.first << " " << pair.second << endl;
}
13
Chris Redford

Chris Redfordの答えは、Qtコンテナでも機能します(もちろん)。これが適応です(const_iteratorメソッドからconstBegin()、それぞれconstEnd()を返します):

class MyCustomClass{
    QList<MyCustomDatatype> data_;
public:    
    // ctors,dtor, methods here...

    QList<MyCustomDatatype>::iterator begin() { return data_.begin(); }
    QList<MyCustomDatatype>::iterator end() { return data_.end(); }
    QList<MyCustomDatatype>::const_iterator begin() const{ return data_.constBegin(); }
    QList<MyCustomDatatype>::const_iterator end() const{ return data_.constEnd(); }
};
1
user2366975

ここでは、「range-based for loop」で機能するカスタムタイプを作成する最も簡単な例を共有しています。

#include<iostream>
using namespace std;

template<typename T, int sizeOfArray>
class MyCustomType
{
private:
    T *data;
    int indx;
public:
    MyCustomType(){
        data = new T[sizeOfArray];
        indx = -1;
    }
    ~MyCustomType(){
        delete []data;
    }
    void addData(T newVal){
        data[++indx] = newVal;
    }

    //write definition for begin() and end()
    //these two method will be used for "ranged based loop idiom"
    T* begin(){
        return &data[0];
    }
    T* end(){
        return  &data[sizeOfArray];
    }
};
int main()
{
    MyCustomType<double, 2> numberList;
    numberList.addData(20.25);
    numberList.addData(50.12);
    for(auto val: numberList){
        cout<<val<<endl;
    }
    return 0;
}

私のような初心者の開発者に役立つことを願っています:p :)
ありがとうございました。

1
RajibTheKing