Clang の Generic Lambdas で遊んでみた

ひげの生えたおじさんからおもちゃをもらいました。
やたー。


と、いうことで C++1y の Generic Lambdas(仮)が実装された Clang があるらしいのでちょっと遊んでみました。


Generic Lambdas まじやばい。

[動作環境]

さて、わたしは Windows 環境なんですが、Windows 環境で Generic Lambdas を試す場合、

  • 自力で github のソースをビルドする
  • サイトで配布されているバイナリを使用する


があるんですが、今回はサイトで配布されているバイナリを使用しています。
Windows だと Visual Studio でビルドされたバイナリ(generic-lambda-clang.exe と lli.exe)が上記のサイトで配布されています


余談ですが、手元の gccgithub に上がっている Clang のソースからビルドしてみようとしたんですが、最後にリンクエラーが出てしまい無理でした。原因はよく分からず。
あと github にあるソースはちょくちょく更新されているので、突っ込んだ使い方をする場合はそっちを使ったほうがいいと思います。


ちなみに動作テスト方法はこんな感じらしい。

$ generic-lambda-clang -std=c++1y -c test.cpp -emit-llvm -o test.bc
$ lli test.bc

[簡単な使い方]

とりあえず、使ってみる。

#include <cassert>

template<typename T>
struct X{
    X
    operator +(X<T> const& x) const{
        return { value + x.value };
    }

    int value;
};


int
main(){
    // 型を定義する変わりに auto を使用することが出来る
    auto twice = [](auto a){
        return a + a;
    };
    assert(twice(4) == 8);
    assert(twice(1.25) == 2.5);


    // {} や return を省略して定義することも出来る
    auto get_value = [](auto x) x.value;
    assert(get_value(X<int>{7}) == 7);


    // 関数テンプレートのように typename T を使用することが出来る
    auto plus_X = []<typename T>(X<T> x, T n) x.value + n;
    assert(plus_X(X<int>{5}, 6) == 11);


    // auto ではなくて typename T を使ってみたり
    auto plus = []<typename T>(T a, T b) a + b;
    assert(plus(2, 3) == 5);


    // 明示的に参照を返すように定義したり
    auto at_value = [](auto& x)->auto& x.value;
    X<int> x{6};
    assert(at_value(x) == 6);

    at_value(x) = 15;
    assert(x.value == 15);

    return 0;
}


Generic Lambdas がやばすぎてやばい。
単に auto キーワードが使えるだけかと思ってたら普通の関数テンプレートのように typename T が使えたり、型推論出来たり、さらに {} や return を省略して書けたりと C++11 から比べてパワーアップし過ぎでは。
再帰や多重定義が出来ない以外は殆ど関数テンプレートと同じような使い方が出来ますね、これ。
と、いうことでもうちょっと遊んでみました。

SFINAE

enable_if を使って SFINAE したり。

#include <cassert>
#include <type_traits>

int
main(){
    auto func =
    []<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
    (T n){
        return n + n;
    };

    // こういう書き方も可
//  auto func = [](auto n, typename std::enable_if<std::is_integral<decltype(n)>::value>::type* = 0){
//      return n + n;
//  };

    // ok
    assert(func(6u) == 12u);
    assert(func(-5L) == -10L);

    // error: no matching function for call
//  func(3.14);
//  func(nullptr);
    return 0;
}


殆ど関数テンプレートと同じような使い方ですね。
多重定義が出来ないので、これだけだと失敗した場合に別の関数呼び出しが行えないのがちょっと残念ですが。

Variadic Templates

Variadic Templates も使うことが出来ました。
パネェ。

#include <cassert>
#include <type_traits>
#include <array>


int
main(){
    auto make_array =
        []<typename T, typename ...Args, typename Result = std::array<T, sizeof...(Args)+1>>
        (T const& t, Args const&... args)
        ->Result{
            return {{ t, args... }};
        };

    // github のソースをビルドした場合はこう書ける
//  auto make_array =
//      []<typename T, typename ...Args>
//      (T const& t, Args const&... args)
//      ->std::array<T, sizeof...(Args)+1>{
//          return {{ t, args... }};
//      };

    auto v = make_array(1, 2, 3, 4);

    static_assert(std::is_same<decltype(v), std::array<int, 4>>::value, "");
    assert(v[0] == 1);
    assert(v[1] == 2);
    assert(v[2] == 3);
    assert(v[3] == 4);
    
    return 0;
}


Variadic Templates が使えるとは思ってなかったのでこれは驚いた。
ここまで出来るとなんでも出来そう。

不動点コンビネータ

だいぶすっきりと書けますね。

#include <cassert>

int
main(){
    auto fix = [](auto func){
        return [=]<typename... Args>(Args... args){
            return func(func, args...);
        };
    };

    auto fact = fix([](auto f, int n)->int{
        return 1 >= n ? 1 : n * f(f, n-1);
    });

    assert(fact(3) == 6);
    assert(fact(5) == 120);
    
    return 0;
}


と、いう感じで Generic Lambdas で遊んでみました。
Generic Lambdas やばい。
あとついでに constexpr と decltype に対応しないかなー。
C++1y はよ。

おまけ

こんな感じで再帰+多重定義でヒャッハー!しようとしてみたんですが、コンパイラが終了しなくなりました(´・ω・`)

#include <utility>

template<typename ...Args>
struct overload;


template<typename T>
struct overload<T> : T{
    using T::operator();

    template<typename TT>
    overload(TT&& t) : T(std::forward<TT>(t)){}
};


template<typename T, typename U, typename ...Args>
struct overload<T, U, Args...>
    : T
    , overload<U, Args...>{

    using T::operator();
    using overload<U, Args...>::operator ();
    
    template<typename TT, typename... TArgs>
    overload(TT&& t, TArgs&&... args)
        : T(std::forward<TT>(t))
        , overload<U, Args...>(std::forward<TArgs>(args)...){}
};


template<typename... Funcs>
overload<typename std::decay<Funcs>::type...>
make_overload(Funcs&&... funcs){
    return { std::forward<Funcs>(funcs)... };
}


int
main(){
    auto fix = [](auto func){
        return [=]<typename... Args>(Args... args){
            return func(func, args...);
        };
    };

    auto sum = fix(make_overload(
        []<typename T>
        (auto f, T t)->T{
            return t;
        },
        []<typename T, typename U, typename... Args>
        (auto f, T t, U u, Args... args)->T{
            return t + f(f, u, args...);
        }
    ));
    
    assert(sum(1)          == 1);
    assert(sum(1, 2)       == 3);
    assert(sum(1, 2, 3, 4) == 10);

    return 0;
}