背景
C++において関数(関数オブジェクト)を渡して動作をカスタマイズするという設計はよくあると思います。
実際、標準ライブラリ<algorithm>の関数群はそうなっています。
例えば、次のようにソートの第3引数に関数オブジェクトを渡すことはよくあるかと思います。
cpp
1#include <vector> 2#include <algorithm> 3#include <functional> 4 5int 6main() { 7 std::vector v{ 1, 2, 3, 5, 9, 4 }; 8 std::sort(begin(v), end(v), std::greater<int>{}); 9 // v = [ 9, 5, 4, 3, 2, 1 ] 10}
問題提起
greater<int>{}
のようにいちいちテンプレートの<>
を書くのがかっこ悪いです。
なんなら{}
も邪魔くさいです。
greater
と書けると嬉しいです。
質問
何らかのfoo
という作用があるとします。
foo
はジェネリックなものとします。
foo
を関数としても関数オブジェクトとしても使いやすくするためにはどういう設計をすれば良いのでしょうか?
なにか良い方法が無いものでしょうか?
やはりプリプロセッサマクロなのでしょうか???
yohhoyさんの回答を受けて(追記)
次のように関数オブジェクトを定義し、その中でオーバーロードを行う。
その上で、あらかじめinline変数として関数オブジェクトのインスタンスを作っておく。
オーバーロードが自然にできるし、関数としても関数オブジェクトとしても使い勝手がいい。
これは、range-v3というC++20に提案されているライブラリの実験的実装でも使われている方法であった。
例えばこのソースにみることができる。
もしかしたらこれが現状(C++17)におけるベストプラクティスなのかもしれないですね。
cpp
1struct foo_t { 2 // generic version 3 template <class T> 4 T operator()(T a) { return a + a; } 5 // specialized version 6 int operator()(int a) { return a * 10; } 7}; 8 9inline constexpr auto foo = foo_t{};
試したこと
関数テンプレート
この方法は関数テンプレートを渡す際にどの実態化なのかを示すためテンプレートパラメータが必須になります。
cpp
1template < class T > 2T foo(T a) { return a + a; } 3 4std::vector<int> v{ ... }; 5std::vector<int> result; 6std::transform( begin(v), end(v), std::back_inserter(result), foo<int> ); // bad
関数オブジェクト
transparentな関数オブジェクトを使う方法。
コンストラクトしないと使えないが、テンプレートじゃないだけ多少マシ。
cpp
1struct foo { 2 template < class T > 3 T operator()(T a) { return a + a; } 4}; 5std::vector<int> v{ ... }; 6std::vector<int> result; 7std::transform( begin(v), end(v), std::back_inserter(result), foo{} );
ただ、普通の関数としてはダメ。
cpp
1foo{}(42); // bad 2// ^
ラムダ式のラッパー
よくある方法として、transparentなラムダ式で関数テンプレートの呼び出しをラップするというものです。
欠点は、記述量が多すぎることです。
これを書くのはとてもとても面倒なことです。
あと、std::forward<decltype(a)>(a)
がわからないとよく言われるので地味に困ります。
cpp
1// シンプル 2std::transform( begin(v), end(v), std::back_inserter(result), [](auto a){ return foo(a); } ); 3 4// 完全転送 5std::transform( begin(v), end(v), std::back_inserter(result), 6 [](auto&& a){ return foo(std::forward<decltype(a)>(a)); } );
Globalラムダ式
関数オブジェクトのインスタンスを予め作っておくというアイデアですが、クラスを書くのも面倒なのでラムダ式を書きます。
関数テンプレートを書く代わりにラムダ式を書くことにします。
cpp
1inline auto foo = [](auto a){ return a + a; }; 2 3std::transform( begin(v), end(v), std::back_inserter(result), foo ); // nice!
副次効果として、C++17からラムダ式のoperator()がconstexpr指定されるのでこれいいなぁと思ったのですが、
ラムダ式にしてしまうとオーバーロードしたくなっても簡単にはできないのです。
cpp
1inline auto foo = [](auto a){ return a + a; }; 2inline auto foo = [](int a){ return a + 1; }; // redefinition!
回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/10/30 05:03
2018/11/08 08:16 編集
2018/10/31 05:45