LoginSignup
9
13

More than 5 years have passed since last update.

C++11でストロング・ブレーク

Last updated at Posted at 2015-12-17

この記事はADVENTARのC++ Advent Calender 2015の18日目の記事です。

ところで、当投稿はQiitaのC++ Advent Calendar 2015の記事ではありません。
実は、ADVENTARへ投稿を登録した当記事が思ったより膨らんだため、分割して一部をQiitaのC++カレンダーへ投稿させて頂いております。

1.はじめに

私は、折角のC++をベターCとして長年使ってましたが、去年、今更ですがC++11規格の存在を知りC++の発展にびっくり。バリバリ使い始めて、多少は役に立ちそうな小技ツールをいくつか作ったので、その中から2つ、ご紹介させて頂きます。
もし、お役に立つことがありましたら、幸いです。

2.まずは軽くstd::extent<>用マクロ

C++11で規格された多次元生配列の要素数を取ってくるstd::extent<>は便利なのですが、ラムダ式の中などで参照になってしまうと使えません。参照をstd::remove_reference<>で外せば良いのですが、滅多矢鱈長くなるので嫌らしいです。
こんな時は、タイプ量を減らすためにマクロ化です。EXTENT()は1次元用、EXTENT_N()は多次元用です。
使い方は次節を参照下さい。早速ラムダ式の中で使ってます。

extent.h
#include <type_traits>

#define EXTENT(dType)           \
    std::extent<typename std::remove_reference<decltype(dType)>::type, 0>::value
#define EXTENT_N(dType, dRank)  \
    std::extent<typename std::remove_reference<decltype(dType)>::type, dRank>::value

3.本命、ストロング・ブレーク

多重ループやループの中のswitchからループ脱出に苦労された方はいませんか? そんなあなたに朗報(?)です。

C++11でサポートされたラムダ式は、return;で呼び出し元に戻ります。そして、ラムダ式は定義しているところから、そのまま直接呼び出すことができます。これを使ってラムダ式をその場実行し、内部の多重ループからreturn;すると、あら不思議、多重ループをブレークできます。

更に戻り値を返却できるので、最後まで回ったら0、ブレークしたら0以外を返却すれば、抜けたところでブレーク処理や非ブレーク処理を記述できます。
ポイントだけ書くと⇩こんな感じです。

if ( int ret = [&]()->int { {ループ{ブレークしたい時return x;} } return 0;}() )
{
  ブレーク時の処理;
}
else
{
  最後まで回った時の処理;
}

太文字部分がラムダ式です。ラムダ式を定義してそのまま実行し、その戻り値をret変数へ設定しています。
「ここでループ」の中で、return x;することによりループやswitchを全て中断し、xをretへ設定します。
このret変数は、then節内でも参照できます。(else節内でももちろん参照できますが、常に0なので意味ないです。)

マクロを使って下記のように書けるようにしてみました。

TRY(ret)
{
    ループ
    {
        ブレークしたい時、BREAK(x); // ただし、xはint型でx!=0
    }
}
BROKEN()
{
    ブレーク時の処理; // retにBREAK(x)のxが設定されている。
}
NOT_BROKEN()
{
    最後まで回った時の処理; // retは常に0
}

以下、ソースです。有効行はたった5行です。でもこの5行だけだと、確実にハマるのでコメントを付けてます。

strong_break.h
// ***************************************************************************
//      ストロング・ブレーク
//          使い方例①:
//              TRY(ret)        // 内部でint ret;と宣言されている。
//              {
//                  while(...)
//                  {
//                      :
//                      while(...)
//                      {
//                          :
//                          switch(...)
//                          {
//                              :
//              BREAK(1);       // retに1が設定される。
//                              :
//                          }
//                          :
//                      }
//                      :
//                  }
//              }
//              BROKEN()        // BREAK(x);した。(ただし、x!=0)
//              {
//                  std::cout << "BROKEN(ret=" << ret << ")\n";
//              }
//              NOT_BROKEN()    // ループを最後まで回った(もしくはBREAK(0);した。)
//              {
//                  std::cout << "NOT_BROKEN\n";
//              }
//
//          使い方例②(BROKENのみ):
//              TRY(ret)        // 内部でint ret;と宣言される。
//              {
//                  :
//              BREAK(2);       // retに2が設定される。
//                  :
//              }
//              BROKEN()        // BREAK(x);した。(ただし、x!=0)
//              {
//                  std::cout << "BROKEN(ret=" << ret << ")\n";
//              }
//
//          使い方例③(COMPLETEDのみ):
//              TRY(ret)        // 内部でint ret;と宣言される。
//              {
//                  :
//              BREAK(3);       // retに3が設定される。
//                  :
//              }
//              COMPLETED()     // ループを最後まで回った(もしくはBREAK(0);した。)
//              {
//                  std::cout << "COMPLETED()\n";
//              }
// ***************************************************************************

#pragma once

#define TRY(dVar)       if (int dVar=[&]()->int {
#define BREAK(dValue)   return dValue
#define BROKEN()        return 0;}())
#define NOT_BROKEN()    else
#define COMPLETED()     return 0;}()){}else

以下使用例です。

strong_break.cpp
#include <iostream>
using namespace std;

#include "strong_break.h"
#include "extent.h"

int main()
{
    int const values[]={4,2,9,3,6};

    // 単ループから脱出し、ブレークした時に処理
    TRY(ret)
    {
        for (int i=0; i < EXTENT(values); ++i) {
            if (values[i] == 3)
    BREAK(i+1);
        }
    }
    BROKEN()
    {
        std::cout << "BROKEN(ret=" << ret << ")\n";
    }

    // 単ループから脱出し、ブレークした時としなかった時の両方で処理
    TRY(ret)
    {
        for (int i=0; i < EXTENT(values); ++i) {
            if (values[i] == 10)
    BREAK(i+1);
        }
    }
    BROKEN()
    {
        std::cout << "BROKEN(ret=" << ret << ")\n";
    }
    NOT_BROKEN()
    {
        std::cout << "NOT_BROKEN(ret=" << ret << ")\n";
    }

    // 単ループから脱出し、最後まで実行した時(=ブレークしなかった時)に処理
    TRY(ret)
    {
        for (int i=0; i < EXTENT(values); ++i) {
            if (values[i] == 10)
    BREAK(i+1);
        }
    }
    COMPLETED()
    {
        std::cout << "COMPLETED(ret=" << ret << ")\n";
    }

    // 多重ループからの脱出
    int const values2D[][2]={{4,0},{2,6},{9,7},{3,2},{6,1}};
    TRY(ret)
    {
        for (int i=0; i < EXTENT_N(values2D, 0); ++i) {
            for (int j=0; j < EXTENT_N(values2D, 1); ++j) {
                if (values2D[i][j] == 6) {
    BREAK(i+1);
                }
            }
        }
    }
    BROKEN()
    {
        std::cout << "BROKEN(ret=" << ret << ")\n";
    }

    // ループ内のswitchからのループ脱出
    TRY(ret)
    {
        for (int i=0; i < EXTENT(values); ++i) {
            switch(values[i])
            {
            case 0:
                break;
            case 1:
                break;
            case 9:
    BREAK(i+1);
            }
        }
    }
    BROKEN()
    {
        std::cout << "BROKEN(ret=" << ret << ")\n";
    }

    return 0;
}

C++11対応が必要です。MSVC 2015とMinGW 4.9.2の-std=c++11オプション付きで動作確認しています。

【閑話休題】
本当は、TRY {...} CATCH(int e) {...}みたいに書けるようにしたかったのですが、私の頭ではいくら考えてもできそうになかったので諦めました。
TRY(int ret)みたいに「戻り値の型」をユーザが定義するようにもできる筈ですが、使うのが難しくなりそう(boolへの暗黙の型変換が必須)な割にメリットが少ないのでこちらも断念してます。

4.最後に

正直、かなり意表を突く実装と思います。
勝負ごとなら意表を突くのは良いことですが、プログラミングの世界では重々慎むべきことです。知らない人にとって、非常に迷惑な話ですから。
ですので、業務では使う前に関係者の了解を得ることをお薦めします。
プロジェクトの特性によっては使うべきでないケースもありますので。

因みに、これの簡易版についてteratailで意見を聞いてみたことがあるのですが、可読性の面から使わないとの回答が大半でした。その後見つけたのですが、stackoverflowの4年前のQAで直接ラムダ式を使う案について、同様な議論がされてました。回答も大差ありませんでした。

この時より自然な構文でかけるようにできたし、goto使うよりコードを綺麗に保てるので、私のプロジェクトでは使います。

9
13
4

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
9
13