[C++]ラムダ式をオーバーロードする。

 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で呼び分けなければならない程度に動作が複雑なので、ラムダ式では表現できないのである。とはいえ、頭の片隅にでも記憶しておけばそのうち役に立つかもしれない。