読者です 読者をやめる 読者になる 読者になる

【初心者C++er Advent Calendar 2016 8日目】関数テンプレートと関数オブジェクト

タイトルは釣りです。
初心者C++er Advent Calendar 2016 8日目の記事になります。
8日が空いていたので簡単なものを書きます。
前回の記事を書いたあとに

と、言われたので今回はその捕捉になります。

[関数テンプレート]

前回『残念ながら関数の引数型で auto を使用して型推論を行うことは出来ません』と書きました。
確かに auto を使用して型推論を行うことは出来ませんが、template を使用して型推論を行うことは出来ます。

template<typename A, typename B>
auto
plus(A a, B b){
    return a + b;
}

plus(1, 2);       // int + int = int
plus(1, 3.14f);   // int + float = float
plus(std::string("homu"), std::string("mami"));  // std::string + std::string = std::string

こんな感じに auto ではなくて template を使用して引数型を推論する形になります。 基本的な挙動はラムダ式と同じになります。

[関数テンプレートの問題点]

これだけじゃ味気ないのでもうちょっと突っ込んで関数テンプレートの場合の問題点を上げます。
さて、次のように std::for_eachラムダ式を利用して値を出力することが出来ます。

auto output = [](auto x){ std::cout << x << std::endl; };

std::vector<int> xs = {1, 2, 3, 4, 5};
std::for_each(std::begin(xs), std::end(xs), output);

しかし、これを関数テンプレートで行おうとするとエラーになります。

template<typename T>
void
output(T t){
    std::cout << t << std::endl;
}

std::vector<int> xs = {1, 2, 3, 4, 5};

// error: no matching function for call to 'for_each'
std::for_each(std::begin(xs), std::end(xs), output);

これは関数テンプレートを値として使用する場合に『テンプレートの型を明示する』必要があるためです。
ですので、関数などに関数テンプレートを渡す場合は次のようにテンプレートの型を明示する必要があります。

template<typename T>
void
output(T t){
    std::cout << t << std::endl;
}

std::vector<int> xs = {1, 2, 3, 4, 5};

// output<int> を明示する
std::for_each(std::begin(xs), std::end(xs), output<int>);

これで解決します。

[関数オブジェクトを使用する]

さて、関数テンプレートのテンプレート型を明示化することで解決しましたが、毎回型を記述するのは手間です。
そこで、関数ではなくてクラスを利用して回避します。

// operator () を定義したクラスを定義する
struct output_{
    template<typename T>
    void
    operator ()(T t) const{
        std::cout << t << std::endl;
    }
};

// そのクラスのオブジェクトを用意する
output_ const output;

std::vector<int> xs = {1, 2, 3, 4, 5};
// あたかも関数のように振る舞うオブジェクト
std::for_each(std::begin(xs), std::end(xs), output);

関数ではなくてメンバ関数として定義することで型を明示化しなくてもよいようになります。
また、このように operator () を定義して関数のように振る舞うオブジェクトのことを一般的に関数オブジェクトと呼びます。

[まとめ]

よく『template は難しい』みたいなことを聞きますが、やってることは単なる型推論なだけなので auto を理解してるのであれば template に置き換えるだけなのでそんなに難しくはないと思います。
auto にしてもそうですが template を使用することでどんどん型による依存を減らして行くことが可能になります。