読者です 読者をやめる 読者になる 読者になる

C++でrustとかD言語みたいなfor文

暇だったので作ってみました。
参考にしたのはこのへん。
C++11/14でrange based forで指定回数Loopする - Okiraku Programming
http://agc004.contest.atcoder.jp/submissions/867163

ベンチマークと動作チェック用のコード。

#include <bits/stdc++.h>
using namespace std;

class _in {
    struct my_iterator {
        int it; const bool rev;
        explicit constexpr my_iterator(int it_, bool rev = false) : it(it_), rev(rev) {}
        constexpr int operator*() { return it;}
        constexpr bool operator != (my_iterator& r) { return it != r.it;}
        void operator++() { rev ? --it : ++it;}
    };
    const my_iterator i, n;
  public:
    explicit constexpr _in(int n) : i(0), n(n) {}
    explicit constexpr _in(int i, int n) : i(i, n < i), n(n) {}
    constexpr const my_iterator& begin() { return i;}
    constexpr const my_iterator& end() { return n;}
};


const int MAX_N = 1e7;
void loop1() {
    vector<int> v;
    for(int i = 0; i < MAX_N; ++i)
      v.push_back(i);
}

void loop2() {
    vector<int> v;
    for(int i : _in(MAX_N))
      v.push_back(i);
}

void timer(int n) {
    auto start = chrono::system_clock::now();
    if(n == 0) loop1(); else loop2();
    auto end = chrono::system_clock::now();
    cout << "elapsed: "
         << chrono::duration_cast<chrono::milliseconds>(end - start).count()
         << "msec"
         << endl;
}
int main() {
    cout << "benchmark" << endl;
    for(int i : _in(2)) timer(i);
    cout << "check" << endl;

    for(int i : _in(5)) cout << i << ' '; cout << endl;
    for(int i : _in(5, 10)) cout << i << ' '; cout << endl;
    for(int i : _in(10, 5)) cout << i << ' '; cout << endl;
    return 0;
}

結果は
g++ -std=c++11 -Wallだと

benchmark
elapsed: 245msec
elapsed: 312msec
check
0 1 2 3 4
5 6 7 8 9
10 9 8 7 6
  • O1をつけると
benchmark       
elapsed: 97msec 
elapsed: 92msec 
check           
0 1 2 3 4       
5 6 7 8 9       
10 9 8 7 6      
  • O3だと
benchmark
elapsed: 100msec
elapsed: 76msec
check
0 1 2 3 4
5 6 7 8 9
10 9 8 7 6

ということでデフォルトだと遅いものの-O1以降はほぼ変わらないし-O3だとかえって速いという。
どうせ資料とか読んでもよくわからない気がしますが、なんでなんでしょうね。


さりげなく逆順ループも対応してロバストにしてみました。というかそのせいでデフォルトだと遅いんですよね。-O1つければ変わらないとはいえ、なんとなく悔しいです。
要するにコンストラクタの中でテンプレート関数を実体化できればいいと思うんですが、何か方法はないのでしょうか。

また、constexprとかが大量についてるのは最近覚えたものを使いたがる初心者特有のアレです。

ところで、これ作ったのはいいけど使うんだろうか……。

[追記 2017/3/6]

新バージョン

class in_rev {
    struct It {
        int it;
        explicit constexpr It(int it_):it(it_){}
        int operator*(){return it;}
        bool operator!=(It& r){return it!=r.it;}
        void operator++(){--it;}
    };
    const It i,n;
  public:
    explicit constexpr in_rev(int i,int n):i(i),n(n){}
    const It& begin(){return i;}
    const It& end(){return n;}
};
class in {
    struct It {
        int it;
        explicit constexpr It(int it_):it(it_){}
        int operator*(){return it;}
        bool operator!=(It& r){return it!=r.it;}
        void operator++(){++it;}
    };
    const It i,n;
  public:
    explicit constexpr in(int n):i(0),n(n){}
    explicit constexpr in(int i,int n):i(i),n(n){}
    in_rev rev() { return in_rev(n.it - 1, i.it - 1);};
    const It& begin(){return i;}
    const It& end(){return n;}
};
#include <bits/stdc++.h>
using namespace std;


const int MAX_N = 1e7;
const int MID_N = 2000;
void loop1() {
    vector<int> v;
    for(int i = MID_N; i < MAX_N; ++i)
      v.push_back(i);
}

void loop2() {
    vector<int> v;
    for(int i : in(MID_N, MAX_N))
      v.push_back(i);
}

void timer(int n) {
    auto start = chrono::system_clock::now();
    if(n == 0) loop1(); else loop2();
    auto end = chrono::system_clock::now();
    cout << "elapsed: "
         << chrono::duration_cast<chrono::milliseconds>(end - start).count()
         << "msec"
         << endl;
}
int main() {
    cout << "benchmark" << endl;
    for(int i : in(2)) timer(i);
    cout << "check" << endl;

    for(int i : in(5)) cout << i << ' ';
    cout << endl;
    for(int i : in(5, 10)) cout << i << ' ';
    cout << endl;
    for(int i : in(5, 10).rev()) cout << i << ' ';
    cout << endl;
 //   for(int i : 10_) cout << i << endl;
    return 0;
}

逆順ループをrustと同じように( ).rev()で書けるようにした。
ベンチは

lib ) ./a.out 
benchmark
elapsed: 31msec
elapsed: 30msec
check
0 1 2 3 4 
5 6 7 8 9 
9 8 7 6 5 

でまあ変わらないね。
今回の測定環境はi5skylake + Arch Linux + gcc6.2なのでMsys2よりも全然速いけど。