最終更新日時:

コンパイル時に評価可能なラムダ式であるかを判定する

はじめに

 なんとなく、与えられたラムダ式がコンパイル時に評価可能であるかを判定して実行時には静的な部分と動的な部分を適宜組み合わせたハイブリッドな実装をしたくなることがあるだろう。そして、ざっくりと検索をすると以下の質問とその回答がヒットし、それっぽい実装に出会えた感じになる。

 その実装部を以下に転記する。これは単にSFINAEによりコンパイル時に呼び出し可能であるかを試みて処理を切り替えるというものである。

template<class Lambda, int=(Lambda{}(), 0)>
constexpr bool is_constexpr(Lambda) { return true; }
constexpr bool is_constexpr(...) { return false; }
C++

 しかし、この実装ではラムダ式が変数をキャプチャしている場合にコンパイル時に判定をすることができないし、引数をもつラムダ式には対応していない。あと、メタ関数やconceptsで利用したいという自分の需要も満たさない。そこで、疑似的に与えられたラムダ式がコンパイル時に評価可能であるかを判定するメタ関数を作成する。

実装

 別に実装としてはそこまで複雑ではないため早速以下に実装を示す。まず、キャプチャのないラムダ式は単なる関数オブジェクトであり、デフォルト構築が可能である。そのため、デフォルト構築可能か否かで分岐をし、あとは上記で示したis_constexprの仕組み(SFINAE)を用いて判定するだけである。また、Lambda::operator()により関数オブジェクトについてのメンバ関数ポインタを取得して、ここから引数型を取得して引数の判定を可能にしている。ただし、ラムダ式の引数はデフォルト構築が可能であることを仮定している。

template <class Lambda, bool = std::is_default_constructible_v<Lambda>>
struct is_constexpr_lambda {
    // SFINAEによりラムダ式がコンパイル時に呼び出し可能であるかを判定する(引数はデフォルト構築可能とする)
    template <class F, class R, class... Args, int = (F{}(Args{}...), 0)>
    static auto is_invocable(F, R(F::*)(Args...) const) -> std::true_type;
    static auto is_invocable(...) -> std::false_type;

    static constexpr bool value = decltype(is_invocable(Lambda{}, &Lambda::operator()))::value;
};
template <class Lambda>
struct is_constexpr_lambda<Lambda, false> {
    static constexpr bool value = false;
};
template <class Lambda>
inline constexpr bool is_constexpr_lambda_v = is_constexpr_lambda<Lambda>::value;
template <class Lambda>
concept constexpr_lambda = is_constexpr_lambda_v<Lambda>;
C++

 実際のこれの利用例は以下である。ポイントとしては、関数の引数にラムダ式を設定しないことである。これにより、コンパイル時の評価が可能となる。

template <constexpr_lambda Lambda>
constexpr auto test() {
    // 適当に引数に1を与える
    return Lambda{}(1);
}

template <class Lambda>
constexpr auto test() {
    // 適当に0を返す
    return 0;
}

int main() {
    // コンパイル時に実行可能なラムダ式
    constexpr auto f1 = [](int a) { return 5 + a; };
    constexpr auto xxx = test<decltype(f1)>();

    // コンパイル時に実行不可なラムダ式
    constexpr auto f2 = []() { std::random_device rng;return rng(); };
    constexpr auto yyy = test<decltype(f2)>();

    // コンパイル時に実行不可なキャプチャ付きのラムダ式
    int a = 10;
    auto f3 = [a]() { return 10 + a; };
    constexpr auto zzz = test<decltype(f3)>();

    // 600と出力される
    std::cout << xxx << yyy << zzz<< std::endl;;

    return 0;
}
C++

おわりに

 現実的な用途としては、もう少し一般化して実装をしてもいいが、自分としてはこれで十分であるため、これ以上の一般化は必要になるまで考えないつもりである。