C++ で名前付き引数を実現する

![NP1.gif](https://qiita-image-store.s3.amazonaws.com/0/30255/72d78542-eee5-8b5a-1800-e4b605f0f9e6.gif)

# 引数の意味と順序がわからない
```cpp
Date(7, 8);
```
は 7 月 8 日でしょうか、8 月 7 日でしょうか。

```cpp
ToString(255, 16, true);
```
は `255` を文字列に変換するようですが、`16` と `true` は何を意味するのでしょうか。

**名前付き引数（またはキーワード引数）**をサポートする Python や Ruby, C#, Swift などの言語では、このような引数の意味を明快に表現できます。

```rb
Date(month: 7, day: 8);

Date(day: 8, month: 7);

ToString(255, radix: 16, upperCase: true);
```

C++ には名前付き引数に相当する言語機能はありません。
それでも、既存の文法を用いて名前付き引数の代わりを実装できます。

- [Named Parameter Idiom](https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Named_Parameter)
- [Boost Parameter Library](http://www.boost.org/doc/libs/1_62_0/libs/parameter/doc/html/index.html)

ちなみに、将来の C++ に言語機能として名前付き引数を導入することは提案されています[^1]。


# NamedParameter を使う
本記事で紹介する C++ 名前付き引数ライブラリ [**NamedParameter**](https://github.com/Reputeless/NamedParameter) は

- ヘッダオンリーのシンプルな実装
- 簡潔な宣言と自然な文法
- 既存のコードを破壊しない
- デフォルト引数のサポート
- 参照引数のサポート
- 名前空間のサポート
- constexpr のサポート
- 型の不一致はコンパイルエラーにする

といった特長を持ち、次のようなコードを実現します。

```cpp
int main()
{
	Date(Arg::month = 12, Arg::day = 31);

	Date(Arg::day = 31, Arg::month = 12);

	ToString(255, Arg::radix = 16, Arg::upperCase = true);
}
```

## 使い方

### 1. ヘッダをインクルード
[`"NamedParameter.hpp"`](https://github.com/Reputeless/NamedParameter/blob/master/NamedParameter.hpp) をインクルードします。

```cpp
# include <iostream>
# include "NamedParameter.hpp"

void Date(int month, int day)
{
	std::cout << "month: " << month << " day: " << day << '\n';
}

int main()
{

}
```

### 2. 名前付き引数に使う名前を用意
名前付き引数に使う名前を `NP_MAKE_NAMED_PARAMETER();` マクロで用意します。
名前空間に入れれば、既存の変数名との衝突を防げます。
`month` と `day` という名前付き引数を `Arg` という名前空間に定義しましょう。

```cpp
# include <iostream>
# include "NamedParameter.hpp"

namespace Arg
{
	NP_MAKE_NAMED_PARAMETER(month);
	NP_MAKE_NAMED_PARAMETER(day);
}

void Date(int month, int day)
{
	std::cout << "month: " << month << " day: " << day << '\n';
}

int main()
{

}
```

### 3. 名前付き引数で呼び出す関数を用意
名前付き引数を使って呼び出す関数を定義します。`名前_<型>` が引数の型になります。
引数の実際の値にアクセスするには `.value()` を使います。
`Arg::month_<int>` と `Arg::day_<int>` は型が異なるため、順番を入れ替えたオーバーロードを作れます。

```cpp
# include <iostream>
# include "NamedParameter.hpp"

namespace Arg
{
	NP_MAKE_NAMED_PARAMETER(month);
	NP_MAKE_NAMED_PARAMETER(day);
}

void Date(int month, int day)
{
	std::cout << "month: " << month << " day: " << day << '\n';
}

void Date(Arg::month_<int> month, Arg::day_<int> day)
{
	Date(month.value(), day.value());
}

void Date(Arg::day_<int> day, Arg::month_<int> month)
{
	Date(month.value(), day.value());
}

int main()
{

}
```

### 4.名前付き引数の関数を呼び出す
名前付き引数の関数を呼び出すには、`名前 = 値` というスタイルで実引数を記述します。

```cpp
# include <iostream>
# include "NamedParameter.hpp"

namespace Arg
{
	NP_MAKE_NAMED_PARAMETER(month);
	NP_MAKE_NAMED_PARAMETER(day);
}

void Date(int month, int day)
{
	std::cout << "month: " << month << " day: " << day << '\n';
}

void Date(Arg::month_<int> month, Arg::day_<int> day)
{
	Date(month.value(), day.value());
}

void Date(Arg::day_<int> day, Arg::month_<int> month)
{
	Date(month.value(), day.value());
}

int main()
{
	Date(Arg::month = 12, Arg::day = 31);

	Date(Arg::day = 31, Arg::month = 12);

	Date(12, 31); // 従来通りの呼び出し
}
```
## その他

### メンバへのアクセス
引数の実際の値には `.value()` のほかに `operator*` でもアクセス可能です。
値がメンバを持つ場合、`operator->` でメンバにアクセスできます。
これは [`std::optional`](http://en.cppreference.com/w/cpp/utility/optional) と同じインタフェースです。

```cpp
void Print(Arg::name_<std::string> name)
{
	std::cout << "name:" << *name << '\n';

	std::cout << "length:" << name->length() << '\n';
}
```

### デフォルト引数
記述は少し長くなりますが、通常の C++ と同様に右にあるパラメータからデフォルト引数を与えられます。

```cpp
std::string ToString(int n, Arg::radix_<int> radix = (Arg::radix = 10), Arg::upperCase_<bool> = (Arg::upperCase = true));

int main()
{
	ToString(255, Arg::radix = 16);
}
```

### オブジェクトの構築
`=` の代わりに `()` でコンストラクタの引数を渡すと、それらの値からオブジェクトを構築します。

```cpp
void Print(Arg::name_<std::string> name);

int main()
{
	Print(Arg::name(5, 'A')); // "AAAAA"
}
```

### 参照型
参照型の引数をサポートします。
呼び出し時には `std::reference_wrapper` を渡します。

```cpp
void Increment(Arg::n_<int&> n)
{
	++n.value();
}

size_t Length(Arg::text_<const std::string&> text)
{
	return text.length();
}

int main()
{
	int n = 99;

	Increment(Arg::n = std::ref(n));

	std::cout << n << '\n';


	std::string str = "Hello!";

	std::cout << Length(Arg::text = std::cref(str)) << '\n';
}
```

### 名前付き引数の使用の強制
名前付き引数の使用を強制したい場合には、名前付き引数を使用しない関数をオーバーロード候補から外します。

```cpp
void Date_impl(int month, int day)
{
	std::cout << "month: " << month << " day: " << day << '\n';
}

void Date(Arg::month_<int> month, Arg::day_<int> day)
{
	Date_impl(month.value(), day.value());
}

void Date(Arg::day_<int> day, Arg::month_<int> month)
{
	Date_impl(month.value(), day.value());
}

int main()
{
	Date(Arg::month = 12, Arg::day = 31);

	Date(Arg::day = 31, Arg::month = 12);

	Date(12, 31); // コンパイルエラー
}
```

### Parameter pack
複数の名前付き引数を様々な順番で使用するには、多くのオーバーロードを記述しなければなりません。
Parameter pack を使えば、それを省略できます。

```cpp
namespace Arg
{
	NP_MAKE_NAMED_PARAMETER(x);
	NP_MAKE_NAMED_PARAMETER(y);
	NP_MAKE_NAMED_PARAMETER(z);
}

template <class... Args>
void F(Args&&... args)
{
	const auto t = std::make_tuple(std::forward<Args>(args)...);

	std::cout << "x: " << std::get<Arg::x_<int>>(t).value() << '\n';

	std::cout << "y: " << std::get<Arg::y_<int>>(t).value() << '\n';

	std::cout << "z: " << std::get<Arg::z_<int>>(t).value() << '\n';
}

int main()
{
	F(Arg::x = 10, Arg::y = 20, Arg::z = 30);

	F(Arg::y = 20, Arg::z = 30, Arg::x = 10);

	F(Arg::z = 30, Arg::x = 10, Arg::y = 20);
}
```


# 実行時性能
**NamedParameter** による名前付き引数は、コンパイラの最適化によって、ほとんどの場合で通常の関数呼び出しと同じ実行コードになります。
gcc 6.2 及び clang 3.9 で -O2 オプションを有効にして次の 2 つのコードをコンパイルしましたが、実行コードは変化しませんでした。

```cpp
int Add(int a, int b)
{
	return a + b;
}

void Increment(int& a)
{
	++a;
}

int main()
{
	int a = rand(), b = rand();

	Increment(a);

	return Add(a, b);
}
```

```cpp
namespace Arg
{
	NP_MAKE_NAMED_PARAMETER(a);
	NP_MAKE_NAMED_PARAMETER(b);
}

int Add(Arg::a_<int> a, Arg::b_<int> b)
{
	return a.value() + b.value();
}

void Increment(Arg::a_<int&> a)
{
	++a.value();
}

int main()
{
	int a = rand(), b = rand();

	Increment(Arg::a = std::ref(a));

	return Add(Arg::a = a, Arg::b = b);
}
```

## constexpr サポート
**NamedParameter** は `constexpr` に対応しています。
名前付き引数を使用する前にコンパイル時定数式であったコードは、名前付き引数を使用したあともコンパイル時定数式になります。

```cpp
constexpr int Add(Arg::a_<int> a, Arg::b_<int> b)
{
	return a.value() + b.value();
}

int main()
{
	constexpr int n = Add(Arg::a = 2, Arg::b = 8); // constexpr int n = 10;
}
```

# 実装の解説
[NamedParameter.hpp](https://github.com/Reputeless/NamedParameter/blob/master/NamedParameter.hpp) は 200 行に満たないライブラリです。
ヘッダのコードを追って原理を理解しても良いでしょう。
ここでは、`day` という名前付き引数がどのような仕組みで機能するのか、順を追って簡単に説明します。

## 1.
`NP_MAKE_NAMED_PARAMETER(day);` マクロにより、次のコードが生成されます。

```cpp
constexpr auto day = np::NamedParameterHelper<struct day_tag>{};

template <class Type> using day_ = np::NamedParameterHelper<struct day_tag>::named_argument_type<Type>;
```

## 2.
`NamedParameterHelper<day_tag>` 型のオブジェクト `day` には `operator=` と `operator()` が定義されています。
`day = 31` のように渡された値を `NamedParameter<day_tag, int>(31)` とラップして返します。
`day` はコンパイル時定数であり、見かけと違って `day` の状態は一切変化しないことに注意してください。

```cpp
auto result = (day = 31); // result は NamedParameter<day_tag, int> 型
```

## 3.
名前付き引数を使う関数では、引数の型として `day_<int>` を記述していることでしょう。
これは `NamedParameter<day_tag, int>` です。
したがって、2. で生成された `NamedParameter` を受け取ることができるのです。

```cpp
void Day(day_<int> d) // d は NamedParameter<day_tag, int> 型
{
	std::cout << "day: " << d.value() << '\n';
}

int main()
{
	auto result = (day = 31); // result は NamedParameter<day_tag, int> 型

	Day(result);
}
```

# ためしてみる
GitHub [**NamedParameter**](https://github.com/Reputeless/NamedParameter)
オンライン C++ コンパイラで [**NamedParameter サンプルを実行**](https://paiza.io/projects/e/yryHNHIFa1-0gI8EvhEP2g)


# 参考資料
[^1]: A proposal for named arguments for C++ http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4172.htm