関数オブジェクトの型推論はどちらかというと 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