3

for-range ループ構文で、生のポインターを範囲のように動作させるにはどうすればよいですか。

double five = 5;
double* dptr = &five;
for(int& d : dptr) std::cout << d << std::endl;// will not execute if the pointer is null

動機:

boost::optional(将来のstd::optional) 値を範囲と見なすことができるため、 for range ループ http://faithandbrave.hateblo.jp/entry/2015/01/29/173613 で使用できるようになりまし

私が自分の簡易版を書き直したとき:

namespace boost {
    template <class Optional>
    decltype(auto) begin(Optional& opt) noexcept{
        return opt?&*opt:nullptr;
    }

    template <class Optional>
    decltype(auto) end(Optional& opt) noexcept{
        return opt?std::next(&*opt):nullptr;
    }
}

使用されます

boost::optional<int> opt = 3;
for (int& x : opt) std::cout << x << std::endl;

そのコードを見ながら、生の (null 許容の) ポインターにも一般化できると想像しました。

double five = 5;
double* dptr = &five;
for(int& d : dptr) std::cout << d << std::endl;

通常の代わりにif(dptr) std::cout << *dptr << std::endl;。これは問題ありませんが、上記の他の構文を実現したかったのです。

試み

Optional最初に、上記のバージョンのポインターを作成して動作させようとしましたが、できませんでしbeginた。endそのため、型を明示し、すべてのテンプレートを削除することにしました。

namespace std{ // excuse me, this for experimenting only, the namespace can be removed but the effect is the same.
    double* begin(double* opt){
        return opt?&*opt:nullptr;
    }
    double* end(double* opt){
        return opt?std::next(&*opt):nullptr;
    }
}

ほとんどそこに、それはのために働く

for(double* ptr = std::begin(dptr); ptr != std::end(dptr); ++ptr) 
    std::cout << *ptr << std::endl;

しかし、おそらく同等の for-range ループでは機能しません。

for(double& d : dptr) std::cout << d << std::endl;

2 つのコンパイラが教えてくれます。error: invalid range expression of type 'double *'; no viable 'begin' function available

何が起こっている?範囲ループがポインターに対して機能することを禁止するコンパイラーの魔法はありますか。範囲ループ構文について間違った仮定をしていますか?

皮肉なことに、標準にはオーバーロードがstd::begin(T(&arr)[N])あり、これはそれに非常に近いものです。


注意してください。

はい、その考えはばかげています。

double* ptr = new double[10];
for(double& d : ptr){...}

最初の要素のみを反復します。より明確で現実的な回避策は、@Yakk によって提案された回避策のようなことを行うことです。

for(double& d : boost::make_optional_ref(ptr)){...}

このようにして、1 つの要素のみを繰り返し処理していること、およびその要素がオプションであることは明らかです。

わかりました、わかりました、に戻りif(ptr) ... use *ptrます。

4

3 に答える 3

7

範囲ベースの for が機能する方法は (§6.5.4 から) であるためです。

begin-exprend-exprは次のように決定されます
_RangeTが配列型の場合 [..]
_RangeTがクラス型の場合 [..]
— それ以外の場合、 begin -exprend-exprはそれぞれbegin(__range)とです。関連する名前空間 (3.4.2) で検索されます。[ 注:通常の非修飾ルックアップ (3.4.1) は実行されません。—終わりのメモ]end(__range)beginend

この場合、関連する名前空間は何ですか? (§3.4.2/2、強調鉱山):

名前空間とクラスのセットは、次の方法で決定されます:
(2.1) —Tが基本型の場合、関連する名前空間とクラスのセットは両方とも空です。

したがって、double* begin(double*)範囲ベースのforステートメントによって呼び出されるようなものを配置する場所はありません。

やりたいことの回避策は、単純なラッパーを作成することです。

template <typename T> 
struct PtrWrapper {
    T* p;
    T* begin() const { return p; }
    T* end() const { return p ? p+1 : nullptr; }
};

for (double& d : PtrWrapper<double>{dptr}) { .. }
于 2015-01-30T18:31:01.440 に答える