6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C++ 任意の順番で引数を指定し、任意の引数を省略する関数オブジェクト

Last updated at Posted at 2018-02-18

#デフォルト引数と名前付き引数
VBAでは以下のようにしてプロシージャにデフォルト引数を指定することができる。
Optional は引数が省略可能であることを宣言する。

Sub OptionalArgs(Optional arg1 As Integer = 0, Optional arg2 As Integer = 0)
'...
End Sub

多くの場合において使用される実引数が事前に判明している場合、このようにプロシージャを設計することで呼び出し側のコードが簡潔になる。

OptionalArgs 100, 200
OptionalArgs 100
OptionalArgs

引数が不足している場合末尾から省略されていると解釈され、省略された引数としてデフォルト引数(=0)が使用される。

OptionalArgs arg1:=100, arg2:=200
OptionalArgs arg2:=200, arg1:=100

名前付き引数を使用してプロシージャを呼び出すこともできる。
こちらは任意の順番で引数を指定できるため、任意の引数を省略することができる

#C++で名前付き引数やデフォルト引数を扱う
C++にもデフォルト引数を扱う構文が用意されている。

void func(int arg1 = 0, int arg2 = 0) {
/* ... */
}

一方、名前付き引数を使用した関数呼び出しはサポートされていない。
VBA同様、引数が不足している場合末尾から省略されていると解釈され、省略された引数としてデフォルト引数(=0)が使用される。
必ず引数を後ろから省略する場合、この構文で何ら問題ない。
問題は引数の数が多く、かつどれが省略されるか事前に判断できない場合だ。

void func(int arg1 = 0, int arg2 = 0, int arg3 = HOGE, int arg4 = FUGA, int arg5 = ...)

それぞれの引数についてデフォルトで使用したい値はあるが、省略される引数は先頭の arg1 かもしれない。
arg1 についてデフォルト引数を使用したい場合、呼び出し側のコードは以下のようになる。

func(0, 0, HOGE, FUGA, ...);

愚直に全てのデフォルト引数を呼び出し側に記述するしかない。
デフォルト引数を使用したい引数以降の全てのデフォルト引数を調べて指定する必要がある。
例えばウィンドウを生成する関数を考えている場合、「大きさ」、「タイトル」、「アクティブ状態」、「フォーカス有無」等の多岐にわたる設定を行うための引数を関数が持つことが考えられるが、それらの引数のうちどれが省略されるかは場面によって異なり、省略のされやすさに明確な序列は存在しない。
このような場面において、引数の序列を事前に定義せず、任意の順番で また 任意の引数を省略する ことができる設計はないものか。

#自身の参照を返すsetter
引数から setter に話を移す。以下を見てほしい。

class func {
  int _a;
  int _b;
public:
  func& a(int value) { _a = value; return *this; }
  func& b(int value) { _b = value; return *this; }
};

auto f1 = func().a(1).b(2);

単純な setter が自身の参照を返している。
これによりコンストラクト後、メソッドチェーンを使用して連続的に a, b といった複数の setter を呼び出すことができる。

auto f1 = func().a(1).b(2);
auto f2 = func().b(2).a(1);

注目すべきは上記のように任意の順番で各々の setter を呼び出すことができる点である。
この様式は、先に挙げた VBA の名前付き引数に類似している。

#関数オブジェクトに適用する
任意の順番で setter を呼び出すことができるなら、setter により設定されたメンバ変数を利用して関数を呼び出せるようにすればいい。
関数オブジェクトの出番である。

class func {
  int _a;
  int _b;
public:
  func& a(int value) { _a = value; return *this; }
  func& b(int value) { _b = value; return *this; }
  
  func () : _a(1), _b(1) {} //デフォルト引数

  void operator()() { //関数呼び出しのオーバーロード
    std::cout << (_a * _b) << std::endl;
  }
};

func().a(2).b(3)(); // 2 * 3 -> 6
func().a(2)();      // 2 * 1 -> 2
func().b(3)();      // 1 * 3 -> 3
func()();           // 1 * 1 -> 1

#マクロによるパターンの抽象化
このパターンにおいて「引数と対応するメンバ変数」と「自身の参照を返す setter」は必ず対になって宣言される。
冗長なためマクロを使用して簡略化してみよう。

#define PARAM(type, name) private: type _##name; public: auto name(const type& name) { _##name = name; return (*this); }

class func {
  PARAM(int, a)
  PARAM(int, b)
public:
  func () : _a(1), _b(1) {}

  void operator()() {
    std::cout << (_a * _b) << std::endl;
  }
};

PARAMマクロは「引数と対応するメンバ変数」と「自身の参照を返す setter」に展開される。
展開後のマクロで private: や public: を指定して可視性を変更しているため、PARAMマクロ使用後は public: 等と以降の可視性を明示する必要があることに注意すること。

6
1
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
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?