LoginSignup
7
5

More than 5 years have passed since last update.

値を返すブロック

Posted at

半分以上ギャグです。

値を返すブロック

ある値が生成後変化しないなら、constにしたいのが人の性です。
リテラル値を即代入できる場合や、単純な計算、関数呼び出しの場合はこれはすぐさまできて、

constexpr std::int32_t answer = 42;
constexpr double pi = boost::math::constants::pi<double>();
constexpr double one_div_pi = 1.0 / pi;

となります。
しかし、現実は常にこのような単純な場合だけではなく、値が条件によって変化する場合があるでしょう。同じようにしたくても、C++のif文は値を返さないので以下のようには書けません。

const double factor = if (ptr) {*ptr} else {1.0}; // 無理

とはいえ、単なる条件分岐なら三項演算子が使えて、

const double factor = ptr ? *ptr : 1.0;

とできます。

三項演算子に関しては慣れていないと読みにくいとの意見もありますが、個人的には状況次第でconstになっていないよりは三項演算子の方がマシだと考えています。と言うか三項演算子にはC++11でconstexpr関数を書いていたら嫌でも慣れるのでコンパイル時計算をしましょう。

さて、ではもう少し面倒な場合はどうでしょう。例えば{0, 3, 6, 9, 12, ..., 300}のような値で配列を初期化したいとします。

例えばこれは不可能なコードです。

const std::vector<std::uint32_t> nums = {0, 3, 6, 9, /* ... 頑張る ... */, 300};

マクロに手を染めることによって/* ... 頑張る ... */を生成することが可能でしょうが、マクロに頼りたくはありません。csv#includeするのは忘れてください。

やはり初期化時に直接作るのが一番です。となると、自分でiteratorを作成する……のは面倒なのでBoost.Iteratorを使うと、以下のように書けます。

const auto f = [](std::uint32_t i){return i*3;};
const std::vector<std::uint32_t> nums(
    boost::make_transform_iterator(
        boost::make_counting_iterator<std::uint32_t>(0), f),
    boost::make_transform_iterator(
        boost::make_counting_iterator<std::uint32_t>(101), f)
    );

※ 上でfとしているラムダを個々のmake_transform_iteratorで直接作るとコンパイルに失敗します。おそらく一つ目のラムダと二つ目のラムダが別の型として生成されてしまい、二つのイテレータが別の型になるのでvectorのコンストラクタにマッチしなくなることが原因でしょう。

これは目的に沿ったコードですが、長くそして奇妙です。いやここまでの文脈を共有している皆さんにとっては特に奇妙ではないでしょうが、突然これが出て来ると多くのプログラマは一瞬面食らうでしょう。面食らわれました。

生成するだけなら、標準ライブラリだけを使って以下のようにしてできます。

std::vector<std::uint32_t> nums(101);
std::iota(std::begin(nums), std::end(nums), 0u);
std::transform(std::begin(nums), std::end(nums), std::begin(nums),
               [](const std::uint32_t v) -> std::uint32_t {return v * 3;});

しかし今回はconstにしたいのでした。上のコードは書き換えるのが前提なのでconstにはできません。
例えば上のようにして作ったものをそのまま、

const auto nums = {
    std::vector<std::uint32_t> nums_(101);
    std::iota(std::begin(nums_), std::end(nums_), 0u);
    std::transform(std::begin(nums_), std::end(nums_), std::begin(nums_),
                   [](const std::uint32_t v) -> std::uint32_t {return v * 3;});
    nums_
}; // 無理

のように代入してしまえれば、どんなに楽なことでしょう。ブロックが値を返せたらいいのですが!

と言うわけで値を返せるブロックをご紹介します。ブロックの前に[]を、最後に()をつけてください。それだけでブロックが値を返せるようになります。

const auto nums = []{
    std::vector<std::uint32_t> nums_(101);
    std::iota(std::begin(nums_), std::end(nums_), 0u);
    std::transform(std::begin(nums_), std::end(nums_), std::begin(nums_),
                   [](const std::uint32_t v) -> std::uint32_t {return v * 3;});
    return nums_;
}();

これで、transform_iteratorや他のfancy iteratorを駆使しても作るのが難しい値でも、素直にforループやなんやかんやでゴリっと作成したものを返すだけでconstな値として初期化できます。

よかったですね。

補遺

これが問題のコードですが、

const auto nums = []{
    // なんか色々
    return nums_;
}();

これはラムダ関数をその場で構築して即呼び出したものです。ラムダ抽象構文[](){}のうち、引数や属性、戻り値型を全て省略するなら、そもそもパラメータリスト()を省略できます。[]{/*色々*/ return nums_;}の部分までがそれです。
今回は関数オブジェクトが欲しいのではなく、その戻り値が欲しいわけなので、作って即呼び出しています。呼び出すとき、そもそもパラメータリストを省略しているので渡す引数はなく、最後にある呼び出しの()は空です。
キャプチャもしないので[]も空で構いません。外にある変数を使うためにはキャプチャしなければならないのですが、最初の[]に色々入ってしまってラムダ感丸出しになるので上の例では避けました。

自然な発想の流れとしては、

  1. 関数の戻り値ならそのまま代入できるのに……
  2. でもこの場で使うだけの関数をわざわざ定義するのも面倒だ
  3. そういえばこの場で即関数オブジェクトを作れる機能があるぞ?

と言う流れになります。

ちなみにRustならブロックは値を返すしそれ以前に一度mutで束縛したものに書き込んだ後immutableでシャドーイングしつつ再束縛できます。ほかの部分でboost::variantboost::optionalを使いまくってそれをラップしてexpectedを自作し、リソース管理は全てunique/shared_ptrを使うようにして、SFINAEとstatic_assertでコンセプトのような何かをゴリゴリ頑張っていることを考えると、何でRustで書かなかったの? みたいな感じですね! それでは皆さんよいRustライフを!

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5