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

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

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

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

Q&A

解決済

2回答

653閲覧

関数を引数とした関数テンプレートが理解できない

jbe00214

総合スコア63

C++

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

0グッド

0クリップ

投稿2020/07/25 00:23

発生している問題

次のコードで,test1の場合はエラーは出ないのですが,test2にすると,エラーが出ます。いずれの組合せでも動作すると理解していたのですが,ちょっと理解できなくりました。どなたか教えてください。

該当のソースコード

C++

1namespace test1{ 2 3int funcA(int t, std::function<int(int)>f){ 4 return f(t); 5} 6int funcB(int t, int(*f)(int)){ 7 return f(t); 8} 9int funcC(int t, int f(int) ){ 10 return f(t); 11} 12} 13 14namespace test2{ 15 16template<typename T> 17T funcA(T t, std::function<T(T)>f){ 18 return f(t); 19} 20template<typename T> 21T funcB(T t, T(*f)(T)){ 22 return f(t); 23} 24template<typename T> 25T funcC(T t, T f ){ 26 return f(t); 27} 28} 29 30int a(int x){return x;}; 31auto b=[](int x){return x;}; 32 33 34int main() { 35 using namespace std; 36 37 using namespace test1; 38 39 cout << funcA(10,a)<<'\n';// test2にするとエラー 40 cout << funcB(10,a)<<'\n'; 41 cout << funcC(10,a)<<'\n';// test2にするとエラー 42 43 cout << funcA(10,b)<<'\n';// test2にするとエラー 44 cout << funcB(10,b)<<'\n';// test2にするとエラー 45 cout << funcC(10,b)<<'\n';// test2にするとエラー 46 47 48 return 0; 49} 50 51

試したこと

さらにtest3で試すと funcA とa b, funcBとbの組合せでエラーになって,
test2と状況が異なります。

C++

1 2namespace test3{ 3template<typename T,typename C> 4T funcA(T t, std::function<C(C)>f){ 5 return f(t); 6} 7template<typename T,typename C> 8T funcB(T t, C(*f)(C)){ 9 return f(t); 10} 11template<typename T,typename C> 12T funcC(T t, C f ){ 13 return f(t); 14} 15}

補足情報(FW/ツールのバージョンなど)

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

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

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

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

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

guest

回答2

0

ベストアンサー

c++

1int a(int x){return x;}; 2auto b=[](int x){return x;};

まず前提を整理しておきます:

  • aは通常の**関数(function)**です。
  • bラムダ式(lambda expression)から生成される**クロージャオブジェクト(closure object)**です。

名前空間test1の関数引数では型情報が確定していますから、a,bいずれでも適切な型変換が暗黙に行われます。

  • test1::funcAの第2引数はstd::function<int(int)>型を要求します。
  • test1::funcBの第2引数は関数ポインタint(*)(int)型を要求します。
  • test1::funcCの第2引数は関数int(int)型を要求するように見えますが、C++のルールにより「関数ポインタint(*)(int)型」に読み替えられます。

つまりtest1::funcBtest1::funcCは同一シグネチャを持つ関数です。

c++

1static_assert( 2 std::is_same<decltype(test1::funcB), 3 decltype(test1::funcC)>::value);
  • 関数astd::function<int(int)>型へと変換可能です。
  • 関数aは関数ポインタint(*)(int)型へと変換可能です。
  • クロージャオブジェクトbstd::function<int(int)>型へと変換可能です。
  • ラムダ式[](int x){return x;}何もキャプチャしないため、クロージャオブジェクトbは関数ポインタint(*)(int)型へと変換可能です。

名前空間test2の関数テンプレート引数では型情報が確定していません。つまり実引数a,bからテンプレートパラメータ型推定できるかが論点となります。
残念ながら、プログラミング言語C++の型推論はあなたが期待するほど柔軟ではないというのが大雑把な回答です。

また、test2::funcCの第2引数はTではなくT f(T)を意図していたのではないでしょうか?

C++

1template<typename T> 2T funcC(T t, T f){ // ←ココ 3 return f(t); 4}

下記コード記述を行えば名前空間test2でも全てコンパイル通すこともできます。
https://wandbox.org/permlink/3CqrQylmIetOvP79

C++

1cout << funcA(10, {a}) << '\n'; // {}によりstd::functionの推論ガイドを利用 2cout << funcB(10, a) << '\n'; // (無加工) 3cout << funcC(10, a) << '\n'; // (無加工) 4 5cout << funcA(10, {b}) << '\n'; // {}によりstd::functionの推論ガイドを利用 6cout << funcB(10, +b) << '\n'; // +により関数ポインタ型へ強制変換 7cout << funcC(10, +b) << '\n'; // +により関数ポインタ型へ強制変換

(参考記事:ラムダ式と単項+演算子 - 地面を見下ろす少年の足蹴にされる私

投稿2020/07/25 05:13

編集2020/07/25 06:38
yohhoy

総合スコア6191

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

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

jbe00214

2020/07/25 06:32

早速ありがとうございます。{}と+での強制変換ですね。やってみました。{}は初期化ということですが,+をつけるとなぜ強制変換されるのでしょうか。なお,test2::funcCの第2引数はT f で試したものです。ビャーネストラウストラップ著プログラミング言語C++第4版(§3.4.3)の以下の例示コードを参考にしました。 template<typename C, typename P> int count(const C& c, P pred){ ..... if( pred(x) ) .... } というコードが出て来ます。名前空間test2の T f を, test1では int f のように関数を表現できなかったので,int f(int)としました。なのでtest2は T f での検討でよいのです。
yohhoy

2020/07/25 06:44 編集

> +をつけるとなぜ強制変換されるのでしょうか 回答末尾にある参考記事を参照ください。 > なのでtest2は T f での検討でよいのです。 そのような意図であれば、test3で試したように2種類のテンプレートパラメータを利用すべきです。1つのテンプレートパラメータは1種類の型に推論されるため、test2::funcCではあなたの意図を表現できていません。 template<typename T> T funcC(T t, T f) と template<typename T, typename C> T funcC(T t, C f) は全く意味合いが異なります。
jbe00214

2020/07/25 07:16

了解です。Tはintに推論するけど,Cは int ではなく,int (*)(int)に推論するわけですね。謎が解けました。
guest

0

端的に言えば、 C++ の型推論ルールでは推論できないからです。

テンプレートの実引数推定においては原則として暗黙の型変換は考慮されません。 この場合には a の型 int (*)(int)std::function<T(T)> にマッチしないのでここから T を推論することはできません。

また、推論はそれぞれの仮引数ごとに行われた上で最終的に突き合わせされる (推論が一致しなければマッチしなかったことになる) ので仮引数 t において Tint であることが推論できても std::function<T(T)>T が推論できなければ全体としてはマッチするものがなかったことになります。

投稿2020/07/25 05:05

SaitoAtsushi

総合スコア5444

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

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

jbe00214

2020/07/25 06:53

ありがとうございます。まだあやふやなのですが,名前空間test2::funcCをラムダ式bからの型推論ができなかったのに,test3::funcCでは推論できるようになるのはなぜなのか,お分かりになりますか。
jbe00214

2020/07/25 07:19

謎が解けました。test3では,Tはint に推論し,Cはint(*)(int)に推論されるからですね。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問