関数オブジェクトの型推論はどちらかというと C++17 での進捗はないと思いがちです。
ですが少しですが変化は確認できました。
1 #include <functional>
2 #include <iostream>
3
4 template<class F>
5 struct CallLambda {
6 CallLambda(F&& f){
7 f();
8 }
9 };
10
11 struct CallFunc {
12 CallFunc(std::function<void()> const& func){
13 func();
14 }
15 };
16
17 void cf()
18 {
19 #if defined(__GNUC__) && (__GNUC__ >= 7) && __cplusplus >= 201703L
20 # ifndef __clang__
21 CallLambda([]{std::cout << "lambda" << '\n';});
22 # endif
23 #else
24 CallFunc([]{std::cout << "func" << '\n'; });
25 #endif
26 }
27
28 template<typename TF>
29 struct ScopeGuard : TF
30 {
31 ScopeGuard(TF f) : TF{f} {}
32 ~ScopeGuard() { (*this)(); }
33 };
34
35 void sg()
36 {
37 struct K {
38 void operator()(){ std::cout << "K" << '\n'; }
39 };
40 #if __cplusplus >= 201703L
41 ScopeGuard sg{K{}};
42 #else
43 ScopeGuard<K> sg{K{}};
44 #endif
45 }
46
47 int main()
48 {
49 {
50 cf();
51 sg();
52 }
53 }ビルドと実行(GCC).
$ g++ main.cpp -std=c++17 $ ./a.out lambda K
ビルドと実行(CLANG).
$ clang++ main.cpp -std=c++17 $ ./a.out func K
まずコンパイラ関係なく動くパターンから説明しますね。
28 template<typename TF>
29 struct ScopeGuard : TF
30 {
31 ScopeGuard(TF f) : TF{f} {}
32 ~ScopeGuard() { (*this)(); }
33 };
34
35 void sg()
36 {
37 struct K {
38 void operator()(){ std::cout << "K" << '\n'; }
39 };
40 #if __cplusplus >= 201703L
41 ScopeGuard sg{K{}};
42 #else
43 ScopeGuard<K> sg{K{}};
44 #endif
45 }このケースは C++17 では型推論が行われ、それ以外の規格ではエラーが出たのでテンプレート引数を指定していますね。
これ C++17 で通るのは ScopeGuard が K の派生クラスだからだと… 思います。
もうひとつは謎というほどでも無いですが、筆者が頻繁に不思議に思うラムダ式の引数指定についてです。
4 template<class F>
5 struct CallLambda {
6 CallLambda(F&& f){
7 f();
8 }
9 };
10
11 struct CallFunc {
12 CallFunc(std::function<void()> const& func){
13 func();
14 }
15 };
16
17 void cf()
18 {
19 #if defined(__GNUC__) && (__GNUC__ >= 7) && __cplusplus >= 201703L
20 # ifndef __clang__
21 CallLambda([]{std::cout << "lambda" << '\n';});
22 # endif
23 #else
24 CallFunc([]{std::cout << "func" << '\n'; });
25 #endif
26 }C++ のヘッダーやライブリーのソースを見たことがある人なら CallLambda のほうが動くべきだと思うはずです。
例えば以下のようにコンストラクターでなく関数とすれば GCC でも CLANG でも全く問題ないんです。
1 #include <functional>
2 #include <iostream>
3
4 template<class F>
5 void CallLambda(F&& f){
6 f();
7 }
8
9 void cf()
10 {
11 CallLambda([]{std::cout << "lambda" << '\n';});
12 }
13
14 int main()
15 {
16 {
17 cf();
18 }
19 }これは C++11 の時代からの文法なんでコンパイルは古い規格でやってみます。
$ clang++ main.cpp -std=c++11 $ ./a.out lambda $ g++ main.cpp -std=c++11 $ ./a.out lambda
この通りで、これが普通って感覚なんですが GCC は C++17 でやっとコンストラクターでも推論してくれるようになったってことです。
Copyright 2017-2018, by Masaki Komatsu