質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

4038閲覧

C++における関数の受け渡しについて

mitama_rs

総合スコア165

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

5グッド

8クリップ

投稿2018/10/29 20:24

編集2018/11/02 06:21

背景

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!
yumetodo, strike1217, fa11enprince, opyon, alphya👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

Globalラムダ式
ラムダ式にしてしまうとオーバーロードしたくなっても簡単にはできないのです。

"簡単"かと言われるとかなり微妙ですが、C++17の範囲内 かつ Globalラムダ式 でもオーバーロード相当の振る舞いを記述可能です。

C++

1template<class... Ts> struct overloaded : Ts... 2 { using Ts::operator()...; }; 3template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; 4 5inline constexpr auto foo = overloaded{ 6 // generic version 7 [](auto a){ return a + a; }, 8 // specialized version 9 [](int a){ return a * 10; }, 10}; 11 12 13// 関数として利用 14int r1 = foo(42); 15double r2 = foo(3.14); 16 17// 関数オブジェクトとして利用 18std::transform(begin(v), end(v), std::back_inserter(result), foo);

Demo: https://wandbox.org/permlink/xIEdBjy2Jbkyuwpf

(あまりに技巧的な記述では可読性・保守性を損ねますから、チームメンバのスキル次第なところはあるでしょうね)


ただ、普通の関数としてはダメ。

結合優先度の都合で、明示的な括弧が必要ですね(foo{}(42)(foo{})(42))。マクロを使え下記のような記述もできます。絶対におススメしませんけど。

C++

1struct foo { 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 9#define foo (foo{})

Demo: https://wandbox.org/permlink/Fq280pkwKeAPJV4c

投稿2018/10/30 01:07

編集2018/10/30 01:29
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mitama_rs

2018/10/30 05:03

ありがとうございます。 たしかに、overload setとしてラムダ式をあらかじめ定義しておくという方法がありましたね。 自分のライブラリとして完結できるときに利用できそうです。 やり過ぎ感が否めないですが。
yohhoy

2018/11/08 08:16 編集

後から気づいたのですが、「#define foo (foo{})」ではなく「inline constexpr auto foo = foo_t{};」と書けば、プリプロセッサにたよらずとも前者overloaded相当を実現できますね。 追記:上記コードを実際書いてみると、operator()()メンバ関数側にconst修飾が必要でした。どちらが望ましいかは好みでしょうね...
mitama_rs

2018/10/31 05:45

もしかしたらラムダ式よりいいかもしれないという気がしてきました。
guest

0

greater<int>{}のようにいちいちテンプレートの<>を書くのがかっこ悪いです。

なんなら{}も邪魔くさいです。
greaterと書けると嬉しいです。

typedef してそれぞれ別名つければいいんじゃないかと。

投稿2018/10/29 22:24

y_waiwai

総合スコア87749

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kendji

2018/11/06 09:48

あなた仕事が早そうね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問