質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.99%

C++ 範囲ベースforの中でインデックス番号を取り出す方法

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 3
  • VIEW 14K+

Chironian

C++総合1位

こんにちは。

C++11で対応された範囲ベースfor、すっごく便利なのでよく使っているのですが、たまにインデックス番号も欲しい時があります。
そんな時は、for (size_t i=0; 云々で回してます。ドン臭いし、既に範囲ベースforで書いているコードを書き換えるのも面倒です。

begin(), end()で回せばdistance()が使えることは分かっているのですが、for (size_t i=0; 云々と同様書き換えが面倒なのです。更にバグでイテレータがend()を越えるとちょっと怖いです。

頭でsize_t index=0;してループの最後で++index;もありですが、continue使うとはまりそうです。

範囲ベースforで回している時にインデックス番号をスマートに計算する方法ってないでしょうか?
ありそうな気もするのですが、グーグル先生教えてくれないのです。orz


【閑話休題】
なぜに範囲ベースforはiteratorを返さないのでしょう?
for-range-declaration = *__begin;じゃなくて、for-range-declaration = __begin;なら良かったのに。


【その後】
その後、調べていて、ランダム・アクセス・イレテータでないコンテナの場合、std:distance()はリストを追跡するらしいことが分かりました。ということは同様にoperator[]も追跡する筈ですね。(何を今更なのですが。)
今はvectorで範囲ベースforを使ってますが一部はforward_listへ変更します。これを見越すと、インデックスを使うためにはイテレータ型のfor文へ変更しインデックスも追加する必要があります。for文がごちゃごちゃになり、範囲ベースforとの落差に悶え死にそうでした。しかも、autoの型が変わってしまうし...

個人的にこれらが許せないので、yumetodoさんのリンク先の記事からヒントを得て、ツールを作りました。
考え方は、自作の隠しイテレータをコンパイラへ返して、それがインクリメントされた時にインデックスも一緒にインクリメントするイメージです。
ですので、listでも使えますし、listのイテレータをdistanceした時のような追跡処理も発生しません。そして、autoで推論される型は同じコンテナの要素のままですので、範囲ベースforブロック内の変更も不要です。

#include <iterator>

// ***************************************************************************
//      範囲ベースforにインデックス機能を追加する
// ***************************************************************************

namespace staff_only
{

//----------------------------------------------------------------------------
//      隠しイテレータ・クラス
//----------------------------------------------------------------------------

template<typename tValueType, typename tParentIterator>
class Iterator
{
    tParentIterator mParentIterator;
    std::size_t&    mIndexReference;
public:
    explicit Iterator(tParentIterator iIterator, std::size_t& iIndexReference) :
        mParentIterator(iIterator),
        mIndexReference(iIndexReference)
    { }

    tValueType& operator*() {return *mParentIterator;}
    tParentIterator& operator++()
    {
        ++mParentIterator;
        ++mIndexReference;
        return mParentIterator;
    }
    bool operator==(Iterator const& rhs) {return mParentIterator==rhs.mParentIterator;}
    bool operator!=(Iterator const& rhs) {return mParentIterator!=rhs.mParentIterator;}
};

//----------------------------------------------------------------------------
//      コンテナ
//----------------------------------------------------------------------------

template<class tContainer>
class Indexer
{
    typedef typename tContainer::value_type ValueType;
    typedef typename tContainer::iterator ParentIterator;
    typedef Iterator<ValueType, ParentIterator> InnerIterator;

    // Indexer実体
    tContainer&     mContainer;
    std::size_t&    mIndexReference;
public:
    explicit Indexer(tContainer& iContainer, std::size_t& iIndexReference) :
        mContainer(iContainer),
        mIndexReference(iIndexReference)
    {
        mIndexReference=0;
    }
    InnerIterator begin()
    {
        return InnerIterator(mContainer.begin(), mIndexReference);
    }
    InnerIterator const end() const
    {
        return InnerIterator(mContainer.end(), mIndexReference);
    }
};

//----------------------------------------------------------------------------
//      生配列
//----------------------------------------------------------------------------

template<class tValueType, size_t tDim>
class Indexer<tValueType[tDim]>
{
    typedef tValueType* ParentIterator;
    typedef Iterator<tValueType, ParentIterator> InnerIterator;

    // Indexer実体
    typedef tValueType Container[tDim];
    Container&      mContainer;
    std::size_t&    mIndexReference;
public:
    explicit Indexer(Container& iContainer, std::size_t& iIndexReference) :
        mContainer(iContainer),
        mIndexReference(iIndexReference)
    {
        mIndexReference=0;
    }
    InnerIterator begin()
    {
        return InnerIterator(&mContainer[0], mIndexReference);
    }
    InnerIterator const end() const
    {
        return InnerIterator(&mContainer[tDim], mIndexReference);
    }
};
}   // namespace staff_only

//----------------------------------------------------------------------------
//      API
//----------------------------------------------------------------------------

template<class tContainer>
staff_only::Indexer<tContainer> getIndexer(tContainer& iContainer, std::size_t& iIndexReference)
{
    return staff_only::Indexer<tContainer>(iContainer, iIndexReference);
}

// ***************************************************************************
//      使い方
// ***************************************************************************

#include <array>
#include <vector>
#include <forward_list>
#include <list>

#include <iostream>

int main()
{
    struct TestClass
    {
        int x, y;
    };
    TestClass                       wRawArray[] = {{5,1}, {4,2}, {3,3}, {2,4}, {1,5}, {0,6}};
    std::array<TestClass, 5>        wArray      ={{{4,2}, {3,3}, {2,4}, {1,5}, {0,6}}};
    std::vector<TestClass>          wVector     = {{3,3}, {2,4}, {1,5}, {0,6}};
    std::forward_list<TestClass>    wForwardList= {{2,4}, {1,5}, {0,6}};
    std::list<TestClass>            wList       = {{1,5}, {0,6}};

    std::size_t wIndex;

    std::cout << "raw-array\n";
    for (auto& wData : getIndexer(wRawArray, wIndex)) {
        std::cout << "[" << wIndex << "]=" << wData.x << ", " << wData.y << "\n";
    }

    std::cout << "\nstd::array\n";
    for (auto& wData : getIndexer(wArray, wIndex)) {
        std::cout << "[" << wIndex << "]=" << wData.x << ", " << wData.y << "\n";
    }

    std::cout << "\nstd::vector\n";
    for (auto& wData : getIndexer(wVector, wIndex)) {
        std::cout << "[" << wIndex << "]=" << wData.x << ", " << wData.y << "\n";
    }

    std::cout << "\nstd::forward_list\n";
    for (auto& wData : getIndexer(wForwardList, wIndex)) {
        std::cout << "[" << wIndex << "]=" << wData.x << ", " << wData.y << "\n";
    }

    std::cout << "\nstd::list\n";
    for (auto& wData : getIndexer(wList, wIndex)) {
        std::cout << "[" << wIndex << "]=" << wData.x << ", " << wData.y << "\n";
    }

    return 0;
}

msvc2015とMinGW 5.2.0で動作確認してます。

2点不満が残りました。orz
イテレータはこっそりインクリメントされているので、インデックスもこっそりインクリメントされます。範囲ベースforの仕様とも言えるのでこの点は許容することにしました。
インデックスのスコープがforの外側になってしまいます。内側にしたいのですが、範囲ベースforの構文に割り込めず諦め。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+4

Boost.Rangeライブラリのboost::adaptors::indexedという手段もあります。

#include <forward_list>
#include <iostream>
#include <string>
#include <boost/range/adaptor/indexed.hpp>

int main()
{
  std::forward_list<std::string> list = { "apple", "banana", "cinnamon" };
  for (const auto& e : list | boost::adaptors::indexed()) {
    std::cout << e.index() << ":" << e.value() << std::endl;
  }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/03/02 12:09

    情報ありがとうございます。

    以前情報を頂いたrangeの概念が有効な気がして、丁度boostのrangeを調べてました。

    > auto& e : list | boost::adaptors::indexed()

    しかし、この記法はちょっと衝撃的ですね。
    boost::adaptors空間でoperator|をオーバーロードしているのだと思いますが、とても思いつけない方法です。boostにはいつも驚かされます。

    キャンセル

checkベストアンサー

+2

こんな方法があります。

type name[length];

for( const auto& el : name ){
    size_t index = &el - &name[0];
}


ネイティブな配列、vector、arrayどれでも利用可能です。listでは使えません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/03/02 01:01

    標準規格的に可能かどうか気にしてます。

    その後、調べた結果、std:distance自体がランデム・アクセスでないコンテナの場合、yumetodoさんが書かれたような追跡アルゴリズムで実装されているようです。ということはキャスト的な方法でイテレータを導いても意味ないことが分かりました。
    更にlistの場合operator[]も再追跡していることに今更気がつき、これは許せないので、質問へ追記した方法で実装することにしました。

    キャンセル

  • 2016/03/02 09:52

    > listの場合operator[]も再追跡している
    listにoperator[]は実装されていませんよ?

    細かい点は置いておいて、やろうと思えば可能なのかもしれませんが、コストと比較してメリットが少ないと思います。伝統的forの使い方は誰でも知ってますし、適材適所で使い分ければいいのです。何でも同じ書き方にするのは、ifとgotoの組み合わせでコードを書くようなものです。

    キャンセル

  • 2016/03/02 10:02

    あ、確かに。
    ってことは、そもそもlistに対して、for(size_t)で回してdistanceを使ってインデックス計算なんてできないのですね。あぶないあぶない。
    結局は、「質問」に追記した通りの方向で対策する予定です。
    もうちょっと便利にできそうなので、もしも、うまいこといったらQiitaで公開するかも。

    議論にお付き合い頂き、ありがとうございます。

    キャンセル

+2

ググッて2番目のサイトなのでお読みかもしれませんが
http://stackoverflow.com/questions/10962290/find-position-of-element-in-c11-range-based-for-loop
Matthieu M.さんの回答がなかなか優秀だと思います。

>なぜに範囲ベースforはiteratorを返さないのでしょう? 

いや、個人的には

std::vector<int> v1, v2 ;
for(e1, n1 : v1; e2, n2 : v2){
//do something
}


みたいに書きたいです。
P0026R0: multi-range-based for loops
Tarse Range-Based for(rejectされました)
をごちゃ混ぜにしたような書き方ですが。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/28 23:29

    ありがとうございます。

    そのページはみてませんでした。キーワードが悪かったかも。
    見てみました。仰る通りmajiponiさんの案が軽くて良いですね。

    キャンセル

  • 2016/02/29 00:08

    ちなみに私の検索キーワードは「range based for index」です。

    ポインタの差分を取る方法はmajiponiさん自身の指摘通り、バグの温床になりかねないので、ご利用は計画的に

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.99%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる