はじめに
関数の引数が増えるとデフォルト値が指定してあっても、途中の引数だけでデフォルト値を指定することはできない。そのため、結局デフォルト値を調べながら指定する必要ある。kotlinなど名前付き引数がある言語なら、個別にパラメータを指定できてうらやましいところだが、c++20で指示付き初期化子(designated initialization)が規格され、これを使うと擬似的に名前付き引数が使える。c++17以前版は記事にまとめたので、改めてc++20版としてまとめなおした。
なお、c++17以前の機能を使ったものに関しては以下を参照
https://qiita.com/luftfararen/items/0a0c81ae2418a896691f
実装例と使い方
# include <string>
# include <iostream>
# include <iomanip>
# include <sstream>
# include "named_args.h"
//引数のリストは構造体として定義し、デフォルト値を指定
struct to_str_args
{
int base = 10;
int w=0;
int precision = 5;
char fill = ' ';
bool fixed = false;
};
template<class T>
std::string to_str(T v, const to_str_args& a = to_str_args())
{
std::stringstream ss;
ss << std::setfill(a.fill);
ss << std::setbase(a.base);
ss << std::setw(a.w);
if (a.fixed) ss << std::fixed;
ss << std::setprecision(a.precision);
ss << v;
return ss.str();
}
int main()
{
std::cout << to_str(12.345) << std::endl; //(1)
std::cout << to_str(12.345, {.w = 6 }) << std::endl;//(2)
std::cout << to_str(12.345, {.w = 7, .precision = 3,.fill = '0' }) << std::endl;//(3)
to_str_args a{.w = 7, .precision = 3,.fill = '0' };
std::cout << to_str(12.345, a) << std::endl;//(4)
std::cout << to_str(23.456, a) << std::endl;//(5)
}
結果
12.345
012.345
00012.3
00012.3
00023.5
解説
(1)のように構造体変数[(2)と違い{}そのもの]を省略すれば構造体のデフォルト値が使われる。これはto_str関数の引数の構造体変数に、構造体のコンストラクタをデフォルト値として指定しているため。
(2)(3)では、指示付き初期化子を使うことで擬似的に名前付き引数を実現。designated initializationは{}のなかで、"."+メンバ変数=値で初期化する。各メンバの初期化はカンマで区切る。メンバ変数の順番は構造体の定義順でなくてはいけないが省略は可能。
(4)(5)では、構造体変数をそのものを渡している。定義した関数をconst参照で受けているのは、このケースで効率化を図るため。同じパラメータセットを使いまわすときに便利。
さいごに
「構造体を定義するなら、名前付きに引数にする必要ない」じゃんというツッコミは、許して下さい。呼び出し側の記述量はかなり減らせるはず。あと文字列の書式を整えるなら、c++20からは、std::formatがあるので今回の例はあくまで例ということで。