最近関数へのオプションパラメーターの最適な順序を理解しようとしているときに、 このブログ投稿 と 付随するGitHub repo を偶然見つけました。これはPythonic kwargs
-のヘッダーを提供しますC++のファシリティに似ています。私はそれを使用することにはならなかったが、これは強く型付けされた言語で良いのかどうか疑問に思う。 Python=でしばらくの間作業してきたが、プロジェクト内のkwargs
- like機能の概念は非常に魅力的であることがわかります、残念ながら)、1つまたは2つのパラメーターが異なるコンストラクターの長いリストが生成され、はるかに簡潔/ DRY風にできます。
このようなことを他の人が経験したとしたら、どうですか。避けるべきですか?ガイドラインはありますか?潜在的な問題/落とし穴は何ですか?
私は C++ kwargs についてはあまり詳しくありませんが、ソースをざっと読んだ後、いくつかの欠点が頭に浮かびます。
すべての引数をグローバルに事前宣言する必要があります。ブログ投稿の簡単な例には、次のセクションがあります。
#include "kwargs.h"
// these are tags which will uniquely identify the arguments in a parameter
// pack
enum Keys {
c_tag,
d_tag
};
// global symbols used as keys in list of kwargs
kw::Key<c_tag> c_key;
kw::Key<d_tag> d_key;
// a function taking kwargs parameter pack
template <typename... Args>
void foo(int a, int b, Args... kwargs) {
// first, we construct the parameter pack from the parameter pack
kw::ParamPack<Args...> params(kwargs...);
...
Pythonicのオリジナルほど簡潔ではありません。
C++は、名前付きパラメーターの機能を実現するためのネイティブの代替手段を提供します。
構造ラッパー。オプションのパラメーターを構造体のフィールドとして定義します。
struct foo_args {
const char* title = "";
int year = 1900;
float percent = 0.0;
};
void foo(int a, int b, const foo_args& args = foo_args())
{
printf("title: %s\nyear: %d\npercent: %.2f\n",
args.title, args.year, args.percent);
}
int main()
{
foo_args args;
args.title = "foo title";
args.percent = 99.99;
foo(1, 2, args);
/* Note: in pure C brace initalizers could be used instead
but then you loose custom defaults -- non-initialized
fields are always zero.
foo_args args = { .title = "foo title", .percent = 99.99 };
*/
return 0;
}
プロキシオブジェクト。引数は、チェーンされたセッターで変更できる一時的な構造体に格納されます。
struct foo {
// Mandatory arguments
foo(int a, int b) : _a(a), _b(b) {}
// Optional arguments
// ('this' is returned for chaining)
foo& title(const char* title) { _title = title; return *this; }
foo& year(int year) { _year = year; return *this; }
foo& percent(float percent) { _percent = percent; return *this; }
// Do the actual call in the destructor.
// (can be replaced with an explicit call() member function
// if you're uneasy about doing the work in a destructor)
~foo()
{
printf("title: %s\nyear: %d\npercent: %.2f\n", _title, _year, _percent);
}
private:
int _a, _b;
const char* _title = "";
int _year = 1900;
float _percent = 0.0;
};
int main()
{
// Under the hood:
// 1. creates a proxy object
// 2. modifies it with chained setters
// 3. calls its destructor at the end of the statement
foo(1, 2).title("foo title").percent(99.99);
return 0;
}
注:読みやすさを犠牲にして、ボイラープレートをマクロに抽象化できます。
#define foo_optional_arg(type, name, default_value) \
public: foo& name(type name) { _##name = name; return *this; } \
private: type _##name = default_value
struct foo {
foo_optional_arg(const char*, title, "");
foo_optional_arg(int, year, 1900);
foo_optional_arg(float, percent, 0.0);
...
可変個関数。これは明らかにタイプセーフではなく、正しく機能するためにはタイププロモーションの知識が必要です。ただし、C++がオプションでない場合は、純粋なCで使用できます。
#include <stdarg.h>
// Pre-defined argument tags
enum foo_arg { foo_title, foo_year, foo_percent, foo_end };
void foo_impl(int a, int b, ...)
{
const char* title = "";
int year = 1900;
float percent = 0.0;
va_list args;
va_start(args, b);
for (foo_arg arg = (foo_arg)va_arg(args, int); arg != foo_end;
arg = (foo_arg)va_arg(args, int))
{
switch(arg)
{
case foo_title: title = va_arg(args, const char*); break;
case foo_year: year = va_arg(args, int); break;
case foo_percent: percent = va_arg(args, double); break;
}
}
va_end(args);
printf("title: %s\nyear: %d\npercent: %.2f\n", title, year, percent);
}
// A helper macro not to forget the 'end' tag.
#define foo(a, b, ...) foo_impl((a), (b), ##__VA_ARGS__, foo_end)
int main()
{
foo(1, 2, foo_title, "foo title", foo_percent, 99.99);
return 0;
}
注:C++では、可変長テンプレートを使用してタイプセーフにすることができます。実行時間のオーバーヘッドは、コンパイル時間の低下とバイナリの膨張を犠牲にしてなくなります。
boost :: parameter 。まだサードパーティのライブラリですが、一部のあいまいなgithubリポジトリよりもlibが確立されています。欠点:template-heavy。
#include <boost/parameter/name.hpp>
#include <boost/parameter/preprocessor.hpp>
#include <string>
BOOST_PARAMETER_NAME(foo)
BOOST_PARAMETER_NAME(bar)
BOOST_PARAMETER_NAME(baz)
BOOST_PARAMETER_NAME(bonk)
BOOST_PARAMETER_FUNCTION(
(int), // the return type of the function, the parentheses are required.
function_with_named_parameters, // the name of the function.
tag, // part of the deep magic. If you use BOOST_PARAMETER_NAME you need to put "tag" here.
(required // names and types of all required parameters, parentheses are required.
(foo, (int))
(bar, (float))
)
(optional // names, types, and default values of all optional parameters.
(baz, (bool) , false)
(bonk, (std::string), "default value")
)
)
{
if (baz && (bar > 1.0)) return foo;
return bonk.size();
}
int main()
{
function_with_named_parameters(1, 10.0);
function_with_named_parameters(7, _bar = 3.14);
function_with_named_parameters( _bar = 0.0, _foo = 42);
function_with_named_parameters( _bar = 2.5, _bonk= "Hello", _foo = 9);
function_with_named_parameters(9, 2.5, true, "Hello");
}
最後に、C++には同じことを実現するのに十分な数の代替案があるため、このkwargsライブラリを使用しません。私は個人的に、上記の(完全ではない)リストから1.または2.を選択します。