C++17未満で使える自作Variantを実装する最中、visit関数の動作を調べている中で偶然見つけ、衝撃を受けた。C++のラムダ式は擬似的にオーバーロードすることができる。
#include <iostream> #include <string> template <class ...Ts> struct OverloadedLambda; template <class T, class ...Ts> struct OverloadedLambda<T, Ts...> : public T, OverloadedLambda<Ts...> { OverloadedLambda(T t, Ts ...ts) : T(t), OverloadedLambda<Ts...>(ts...) {} using T::operator(); using OverloadedLambda<Ts...>::operator(); }; template <class T> struct OverloadedLambda<T> : public T { OverloadedLambda(T t) : T(t) {} using T::operator(); }; template <class ...T> OverloadedLambda<T...> Overload(T ...t) { return OverloadedLambda<T...>{ t... }; } int main() { auto lambda = Overload([](int i) { std::cout << "int " << i << std::endl; }, [](double d) { std::cout << "double " << d << std::endl; }, [](const char* c) { std::cout << "char* " << c << std::endl; }); lambda(1);//"int 1" lambda(1.0);//"double 1" lambda("1.0");//"char* 1.0" }
……いや確かに、言われてみればラムダ式は本質的に関数オブジェクトであって、言わば無名クラスのインスタンスなのだから、それを再帰的に継承することでオーバーロードできるのはその通りなのだが、私はこんな発想をしたことがなかった。このことを知った時、私はしばし空いた口が塞がらなかった。
なお、C++17ではOverloadをもっと簡潔に書くことができる。using宣言でのパラメータパック展開が許可されたこと、クラステンプレートのコンストラクタ引数からテンプレート引数推定が可能になったこと、deduction guideという新たな文法が追加されたことによる。そのため再帰継承やヘルパー関数のOverloadそのものが不要となった。
template<class... Ts> struct Overload : Ts... { using Ts::operator()...; }; template<class... Ts> Overload(Ts...) -> Overload<Ts...>;
あるいはこう書いてもいい気がする。
template<class... Ts> struct Overload : Ts... { Overload(Ts...ts) : Ts(ts)... {} using Ts::operator()...; };
でもぶっちゃけ、あんまり実用性はない。私の場合オーバーロードした関数オブジェクトを作らなければならない時は、大抵SFINAEで呼び分けなければならない程度に動作が複雑なので、ラムダ式では表現できないのである。とはいえ、頭の片隅にでも記憶しておけばそのうち役に立つかもしれない。