C++初心者の記事なのでいろいろアレだとは思いますがお許しください.

Rangeライブラリ

C++20ではRangeが新しい標準ライブラリに追加されます.1 残念ながらほとんどのコンパイラは2020年3月現在は対応していないようですが,2 提案者のEric Niebler氏が提案の基礎としてRangeライブラリを作り公開されています.
今回は, そのrange-v3ライブラリを用いてFizzBuzzしてみました. インストールは簡単で, Arch Linuxであれば

sudo pacman -S range-v3

だけで終わりです.
ヘッダオンリーのライブラリのようなので, コンパイル時にリンクさせる必要もありません.

シンプルバージョン

views::iota関数で指定の長さの連続整数列を生成し, views::transform関数で数値か FizzBuzzFizzBuzz に変換して終わりです.
ただ, そのままだと標準出力できないのでviews::all関数を利用しています.3 コードは以下の通り.

#include <iostream>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/transform.hpp>
#include <string>

int main() {
  using namespace ranges;
  std::cout << views::all(views::iota(1, 50) |
                          views::transform([](const auto i) -> std::string {
                            if (i % 15 == 0) {
                              return "Fizz Buzz";
                            } else if (i % 5 == 0) {
                              return "Buzz";
                            } else if (i % 3 == 0) {
                              return "Fizz";
                            } else {
                              return std::to_string(i);
                            }
                          }))
            << std::endl;
}

案外普通な感じでつまんないですね.

おふざけバージョン

シンプルバージョンは面白くなかったので少し凝ってみました. views::transform関数を使うだけだと面白くないので, views::replace_if関数4を用いて順番に FizzBuzzBuzzFizz を生成する感じにしました.
ただ, そのままでは文字列と数値を混在させることはできないので, variantを使って無理やり両方扱わせています.
コードは以下の通り.

#include <iostream>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/replace_if.hpp>
#include <range/v3/view/transform.hpp>
#include <string>
#include <variant>

int main() {
  using namespace ranges;
  std::cout << views::all(views::iota(1, 50) |
                          views::transform([](const auto i) {
                            return std::variant<int, std::string>(i);
                          }) |
                          views::replace_if(
                              [](const auto &i) {
                                return std::holds_alternative<int>(i) &&
                                       std::get<int>(i) % 15 == 0;
                              },
                              "Fizz Buzz") |
                          views::replace_if(
                              [](const auto &i) {
                                return std::holds_alternative<int>(i) &&
                                       std::get<int>(i) % 5 == 0;
                              },
                              "Buzz") |
                          views::replace_if(
                              [](const auto &i) {
                                return std::holds_alternative<int>(i) &&
                                       std::get<int>(i) % 3 == 0;
                              },
                              "Fizz") |
                          views::transform([](const auto &i) {
                            if (std::holds_alternative<int>(i)) {
                              return std::to_string(std::get<int>(i));
                            } else {
                              return std::get<std::string>(i);
                            }
                          }))
            << std::endl;
}

なんかカオスなコードになってきて面白いですね. シンプルバージョンに比べてコンパイル時間もだいぶ増えて, 3秒くらいかかるようになっちゃいました.

分厚い提案書以外の情報があまりないRangeライブラリですが, コンパイラがサポートし始めたらどんどん使っていきたいですね.

参考文献


  1. P0896R4 “The One Ranges Proposal” ↩︎

  2. https://en.cppreference.com/w/cpp/compiler_support ↩︎

  3. ここあんまりよくわかってないのでおまじない感覚で入れてます… ↩︎

  4. この関数はC++20のRangeライブラリに導入されるのかよくわかりません ↩︎