第328章 関数オブジェクトの型推論

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