web-dev-qa-db-ja.com

ラムダを関数テンプレートに渡す

私はC++を学んでいて、述語が保持する最初の要素を見つけるバイナリ検索関数を実装しようとしています。関数の最初の引数はベクトルで、2番目の引数は指定された要素の述語を評価する関数です。二分探索関数は次のようになります。

template <typename T> int binsearch(const std::vector<T> &ts, bool (*predicate)(T)) {
    ...
}

これは、次のように使用した場合、期待どおりに機能します。

bool gte(int x) {
    return x >= 5;
}

int main(int argc, char** argv) {
    std::vector<int> a = {1, 2, 3};
    binsearch(a, gte);
    return 0;
}

しかし、ラムダ関数を述語として使用すると、コンパイラエラーが発生します。

search-for-a-range.cpp:20:5: error: no matching function for call to 'binsearch'
    binsearch(a, [](int e) -> bool { return e >= 5; });
    ^~~~~~~~~
search-for-a-range.cpp:6:27: note: candidate template ignored: could not match 'bool (*)(T)' against '(lambda at
      search-for-a-range.cpp:20:18)'
template <typename T> int binsearch(const std::vector<T> &ts,
                          ^
1 error generated.

上記のエラーは

binsearch(a, [](int e) -> bool { return e >= 5; });

どうしましたか?ラムダが正しい型であるとコンパイラが確信していないのはなぜですか?

23
rodion

関数binsearchは、関数ポインターを引数として受け取ります。 lambda と関数ポインタは異なる型です:ラムダはoperator()を実装する構造体のインスタンスと見なすことができます。

ステートレスラムダ(変数をキャプチャしないラムダ)は、暗黙的に関数ポインターに変換できることに注意してください。ここでは、テンプレートの置換のため、暗黙的な変換は機能しません。

#include <iostream>

template <typename T>
void call_predicate(const T& v, void (*predicate)(T)) {
    std::cout << "template" << std::endl;
    predicate(v);
}

void call_predicate(const int& v, void (*predicate)(int)) {
    std::cout << "overload" << std::endl;
    predicate(v);
}

void foo(double v) {
    std::cout << v << std::endl;
}

int main() {
    // compiles and calls template function
    call_predicate(42.0, foo);

    // compiles and calls overload with implicit conversion
    call_predicate(42, [](int v){std::cout << v << std::endl;});

    // doesn't compile because template substitution fails
    //call_predicate(42.0, [](double v){std::cout << v << std::endl;});

    // compiles and calls template function through explicit instantiation
    call_predicate<double>(42.0, [](double v){std::cout << v << std::endl;});
}

次のように、関数をbinsearchをより汎用的にする必要があります。

template <typename T, typename Predicate>
T binsearch(const std::vector<T> &ts, Predicate p) {

    // usage

    for(auto& t : ts)
    {
        if(p(t)) return t;
    }

    // default value if p always returned false

    return T{};
}

標準アルゴリズムライブラリ からインスピレーションを得てください。

19
wasthishelpful

空のキャプチャリストを含む ラムダ式 は、暗黙的に関数ポインターに変換できます。ただし、関数ポインターpredicateは、パラメーターとしてTを使用しているため、推定する必要があります。型変換はテンプレート型の推定では考慮されません。Tは推定できません。エラーメッセージが言ったように、候補テンプレート(つまり、binsearch)は無視されます。

_operator+_を使用してこれを達成できます。ラムダを関数ポインタに変換します。これは後でbinsearchに渡され、Tが正常に推定されます[1]

_binsearch(a, +[](int e) -> bool { return e >= 5; });
//           ~
_

もちろん、_static_cast_を明示的に使用することもできます。

_binsearch(a, static_cast<bool(*)(int)>([](int e) -> bool { return e >= 5; }));
_

predicateのタイプをTに依存しないように変更する場合、つまりbool (*predicate)(int)の場合、ラムダを空のキャプチャリストに渡しても機能します。ラムダ式は暗黙的に関数ポインターに変換されます。


別の解決策は、関数のポインターから_std::function_にパラメーター型を変更することです。これはファンクターにとってより一般的です。

_template <typename T> int binsearch(const std::vector<T> &ts, std::function<bool (typename std::vector<T>::value_type)> predicate) {
    ...
}
_

その後

_binsearch(a, [](int e) -> bool { return e >= 5; });
_

[1] 正のラムダ: '+ [] {}'-これはどんな魔術ですか?

13
songyuanyao

ラムダが正しい型であるとコンパイラが確信していないのはなぜですか?

テンプレート関数は、テンプレートパラメータを推定するように指示されます変換しない。ラムダは関数ポインターではないため、その引数でTを推定する方法はありません。すべての関数引数が独立してテンプレートパラメーターを推定するため(推定がブロックされていない限り)、これはエラーになります。

あなたが行うことができる多くの修正があります。

テンプレート関数を修正できます。

_template <class T>
int binsearch(const std::vector<T> &ts, bool (*predicate)(T))
_

関数ポインターを_Predicate predicate_または_Predicate&& predicate_に置き換え、本体は変更しないでください。

_template <class T, class Predicate>
int binsearch(const std::vector<T> &ts, Predicate&& predicate)
_

控除ブロックを使用:

_template<class T>struct tag_t{using type=T;};
template<class T>using block_deduction=typename tag_t<T>::type;
template <class T>
int binsearch(const std::vector<T> &ts, block_deduction<bool (*)(T)> predicate)
_

オプションで、関数ポインタをstd::function<bool(T)>で置き換えます。

呼び出しサイトで修正できます。

Tを手動で渡すことができますbinsearch<T>(vec, [](int x){return x<0;})

_+_を+[](int x) ...またはstatic_cast<bool(*)(int)>( ... _)_の前に置くと、ラムダを関数ポインタに減衰させることができます。

最良のオプションはPredicate oneです。これは、標準ライブラリコードが行うことでもあります。

さらに一歩進んで、コードをさらに汎用的にすることもできます。

_template <class Range, class Predicate>
auto binsearch(const Range &ts, Predicate&& predicate)
-> typename std::decay< decltype(*std::begin(ts)) >::type
_

_-> typename std::decay_ ...末尾の戻り値型の部分は、C++ 14では削除できます。

これの利点は、本体が_std::begin_および_std::end_を使用して開始/終了反復子を検索する場合、binsearchdeques、フラットなCスタイルの配列をサポートすることです。 _std::array_ s、_std::string_ s、_std::vector_ s、さらには一部のカスタムタイプ。

binsearchを制御できる場合は、リファクタリングすることをお勧めします。

_template <typename T, typename Predicate>
int binsearch(std::vector<T> const& vec, Predicate&& pred) {
    // This is just to illustrate how to call pred
    for (auto const& el : vec) {
        if (pred(el)) {
            // Do something
        }
    }
    return 0; // Or anything meaningful
}
_

もう1つの方法は、ファンクターオブジェクト/関数ポインター/その他の型消去をstd::function<bool(T const&)>に埋め込むことによって実行することです。そのためには、上記の関数を次のように書き換えます。

_template <typename T>
int binsearch(std::vector<T> const& vec, std::function<bool(T const&)> pred);
_

ただし、テンプレート引数の推定は変換を行わないため、次のように関数に明示的にフィードする必要があります。

_auto my_predicate = [](int x) { return true; }; // Replace with actual predicate
std::vector<int> my_vector = {1, 2, 3, 4};
binsearch(my_vector, std::function<bool (int const&)>(my_predicate));
_

ただし、関数の説明を考えると、_std::find_if_と同じように機能するようです。

_std::vector<int> my_vector = {1, 12, 15, 13, 16};
auto it = std::find_if(std::begin(my_vector), std::end(my_vector),
                       [](int vec_el) { return !vec_el%5; });
// it holds the first element in my_vector that is a multiple of 5.
if (it != std::end(my_vector)) {
    std::cout << *it << std::endl; // prints 15 in this case
}
_

バイナリ検索を行うには、述語だけでなく、範囲とターゲット値の順序を定義する述語が必要です。

3
Rerito

関数ポインタとラムダ関数は同じものではありません。

オブジェクトtを次の述語に割り当てることはできません:

_ bool (*predicate)(int)
_

そして

_auto t = [](int e) -> bool { return e >= 5; });
_

std::function<bool(int)>も使用できます。署名は次のようになります。

_template <typename T> 
int binsearch(const std::vector<T> &ts, std::function<bool(T)> predicate){
   // ...
}
_

これは関数ポインターではありません。必要な場合は、機能ポインターをバインドする必要があります(私はラムダだけで問題ないと思います)

0
Akaedintov