C++
C++11
lambda
const

C++11, C++14でラムダを使って cin から手軽に const 変数を作ろう

More than 1 year has passed since last update.

モチベーション

cosnt変数は変更されないことがわかっているため、読む際の負荷が小さいことが指摘されている。

しかしcinから変数に値を読み込む(変更)際には単純にconstにできない。
更に言うと受け取り変数を用意してから入力を受け取れる規約になっているもの全てでconstにするのが面倒だ。
特にvector<vector<T>> 以上に複雑なオブジェクトの入力受け取りには次で示すような関数を用いた手法では関数の数が増えるため管理が面倒になる。

だったら即時関数(JavaScript風)にすればいいだろうという旨。

c++98 まで

最もよさ気な実装は以下のような関数の定義

template<typename T>
get_value(istream& is) {
  T v;
  is >> v;
  return v;
}

使い方は以下のようにする。

const size_t value = get_value<size_t>(cin);
// const auto value {get_value<size_t>(cin)}; // c++14 or later

問題点として必要とする複雑な入力条件と型の組み合わせによって多くの関数を増やすはめになる。

string get_oneline(istream& is) {
  string s;
  getline(is, s);
  return s;
}

template<typename T>
vector<T> get_oneline_vector(istream& is) {
  const string oneline = get_oneline(is);
  vector<T> value;
  istringstream iss(oneline);
  for (T v; iss >> v;)
    value.push_back(v);
  return value;
}

//! input data structure: c0r0 c0r1 c0r2...
//!                       c1r0 c1r1...
template<typename T>
vector<vector<T>> get_multi_vector(istream& is, size_t col_size) {
  vector<vector<T>> value;
  value.reserve(col_size);
  for (size_t i = col_size; i > 0u; --i)
    value.push_back(get_oneline_vector<T>(is));
  return value;
}

この関数の使い方は以下の通り。

vector<vector<size_t>> value = get_multi_vector<size_t>(cin, 3u);
/**< input are:
1 3 4
2 3
5 6 3 4
*/

ラムダ化する (C++11準拠)

競技などでは一々このような関数を定義しようという気にはなれない。入力処理などプログラム冒頭の数回で出番は終わりである。
仮にバグがあったとしても局所化している方が(短期的には)早くなる。

javascriptの即時関数の如き扱いでスコープを制限してconstをつけてゆく。
具体的にはラムダを定義直後に呼び出すだけだ。

const auto value = []{size_t v; cin >> v; return v;}();
const auto oneline = []{string s; getline(cin, s); return s;}();

//! input is same as above example
const auto multi_vector = []{
  using inner_vector = vector<size_t>;
  constexpr auto size = 3u;
  vector<inner_vector> value;
  value.reserve(size);
  for (auto i = size; i > 0u; --i)
    value.push_back([]{
      auto line = []{string s; getline(cin, s); return s;}();
      inner_vector value;
      istringstream iss(move(line));
      for (size_t v; iss >> v;)
        value.push_back(v);
      return value;
    }());
  return value;
}();

multi_lineには相当無理があるが、全ての変数がconstで定義できている。

初期化キャプチャを伴うlambdaでもっと局所化(c++14準拠)

初期化キャプチャでも即時関数を使える。
予め要素数を入力として渡してもらえる場合に使える手法。paizaでよく使える。

//! input: 5
//!        3 4 2 0 1
const auto values {[size = []{size_t v; cin >> v; return v;}()]{
  vector<int> v(size);
  for (auto& e : v)
    cin >> e;
  return v;
}()};

ラムダなので再利用したい処理は持ち運べる。

//! input: 3
//!        2 3 4
//!        2
//!        5 3 2
//!        2 1 2
const auto inputter {[size = []{size_t v; cin >> v; return v;}()]{
  vector<int> v(size);
  for (auto& e : v)
    cin >> e;
  return v;
}};

const auto base {inputter()};
const auto data {[inputter, size = []{size_t v; cin >> v; return v;}()]{
  vector<vector<int>> vv;
  vv.reserve(size);
  for (auto i {size}; i > 0; --i)
    vv.push_back(inputter());
  return vv;
}()};

ここまでやってなんだが、後で読める気はあまりしない。

注意: 初期化キャプチャの評価順

きちんと確認していないが、初期化キャプチャの評価順は処理系依存動作のはずなので以下のコードは動くとしても可搬性が無いと思われる。

//! input: 3 2
//!        0 2 1
//!        2 1 0
const auto values {[row_size = []{size_t v; cin >> v; return v;}(),
                    col_size = []{size_t v; cin >> v; return v;}()]{
  vector<vector<int>> vv(col_size, vector<int>(row_size));
  for (auto& v : vv)
    for (auto& e : v)
      cin >> e;
  return vv;
}()};

まとめ

  1. 即時関数(ラムダ定義直後に呼び出す)を用いることでスコープを分離するとともに、返り値をconstで受け取ることができる。
  2. どのような入力方法でもこの手法で閉じ込めてconstで受け取れる。
  3. 名前が存在しなくなってゆくのでご利用は計画的に。