web-dev-qa-db-ja.com

C ++ 11はstd :: functionまたはラムダ関数が含まれている場合、型を推測しません

この関数を定義すると、

_template<class A>
set<A> test(const set<A>& input) {
    return input;
}
_

テンプレートタイプを明示的に定義しなくても、コードの他の場所でtest(mySet)を使用して呼び出すことができます。ただし、次の関数を使用すると、

_template<class A>
set<A> filter(const set<A>& input,function<bool(A)> compare) {
    set<A> ret;
    for(auto it = input.begin(); it != input.end(); it++) {
        if(compare(*it)) {
            ret.insert(*it);
        }
    }
    return ret;
}
_

filter(mySet,[](int i) { return i%2==0; });を使用してこの関数を呼び出すと、次のエラーが発生します。

エラー:「filter(std :: set&、main()::)」の呼び出しに対応する関数がありません

ただし、これらすべてのバージョンdoは機能します。

_std::function<bool(int)> func = [](int i) { return i%2 ==0; };
set<int> myNewSet = filter(mySet,func);

set<int> myNewSet = filter<int>(mySet,[](int i) { return i%2==0; });

set<int> myNewSet = filter(mySet,function<bool(int)>([](int i){return i%2==0;}));
_

_std::function_を直接作成せずにラムダ関数を式の内部に直接配置すると、c ++ 11がテンプレートタイプを推測できないのはなぜですか?

編集:

コメントでのLuc Dantonのアドバイスに従って、テンプレートを明示的に渡す必要のない、以前に持っていた関数の代替案を以下に示します。

_template<class A,class CompareFunction>
set<A> filter(const set<A>& input,CompareFunction compare) {
    set<A> ret;
    for(auto it = input.begin(); it != input.end(); it++) {
        if(compare(*it)) {
            ret.insert(*it);
        }
    }
    return ret;
}
_

これは、テンプレートを必要とせずにset<int> result = filter(myIntSet,[](int i) { i % 2 == 0; });から呼び出すことができます。

コンパイラは、新しいdecltypeキーワードと新しい関数の戻り値の型の構文を使用して、戻り値の型をある程度推測することもできます。これは、1つのフィルタリング関数と、値に基づいてキーを生成する1つの関数を使用して、セットをマップに変換する例です。

_template<class Value,class CompareType,class IndexType>
auto filter(const set<Value>& input,CompareType compare,IndexType index) -> map<decltype(index(*(input.begin()))),Value> {
    map<decltype(index(*(input.begin()))),Value> ret;
    for(auto it = input.begin(); it != input.end(); it++) {
        if(compare(*it)) {
            ret[index(*it)] = *it;
        }
    }
    return ret;
}
_

また、テンプレートを直接使用せずに呼び出すこともできます。

_map<string,int> s = filter(myIntSet,[](int i) { return i%2==0; },[](int i) { return toString(i); });
_
54
Datalore

問題はラムダの性質にあります。これらは、標準に従ってプロパティの固定セットを持つ関数オブジェクトですが、not関数です。規格では、ラムダを引数の正確なタイプと、状態がない場合は関数ポインターを使用して_std::function<>_に変換できると決定しています。

しかし、それはラムダが_std::function_でも関数ポインタでもないという意味ではありません。これらはoperator()を実装するユニークなタイプです。

一方、型の推論は、変換なし(const/volatile修飾以外)で正確な型のみを推論します。ラムダは_std::function_ではないので、コンパイラは呼び出しで型を推定できません:filter(mySet,[](int i) { return i%2==0; });を_std::function<>_のインスタンス化にできません。

他の例のように、最初の例ではラムダを関数型に変換してから渡します。コンパイラーは、_std::function_が同じ型の右辺値(一時)である3番目の例のように、そこで型を推定できます。

テンプレートにインスタンス化型intを指定した場合、2番目の実際の例では、演繹が機能しなくなり、コンパイラーはその型を使用してラムダを適切な型に変換します。

あなたのケースを忘れてください。分析するには複雑すぎるためです。

この簡単な例を見てみましょう:

_ template<typename T>
 struct X 
 {
     X(T data) {}
 };

 template<typename T>
 void f(X<T> x) {}
_

fを次のように呼び出します:

_ f(10); 
_

ここでは、Tintandに演繹されると考えたくなるかもしれません。したがって、上記の関数呼び出しは機能するはずです。まあ、そうではありません。簡単にするために、intを次のように取るanotherコンストラクタがあると想像してください。

_ template<typename T>
 struct X 
 {
     X(T data) {}
     X(int data) {} //another constructor
 };
_

f(10)と書いた場合、_Tは何に推定されますか?まあ、Tany型でした。

他にもこのようなケースがたくさんあることに注意してください。この専門化を例にとります:

_ template<typename T>
 struct X<T*>         //specialized for pointers
 {
    X(int data) {}; 
 };
_

次に、f(10)を呼び出すためにTを推定する必要がありますか?今ではさらに難しいようです。

したがって、これは推論不可能なコンテキストであり、同じケースである_std::function_に対してコードが機能しない理由を説明しています。表面的には複雑に見えます。 lambdasは_std::function_型ではないことに注意してください。これらは基本的にコンパイラ生成クラスのインスタンスです(つまり、_std::function_よりも異なる型。

14
Nawaz

私たちが持っている場合:

template <typename R, typename T>
int myfunc(std::function<R(T)> lambda)
{
  return lambda(2);
}

int r = myfunc([](int i) { return i + 1; });

コンパイルされません。しかし、以前に宣言した場合:

template <typename Func, typename Arg1>
static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr) -> decltype((*func)(*arg1));

template <typename Func>
int myfunc(Func lambda)
{
  return myfunc<int, decltype(getFuncType<Func, int>())>(lambda);
}

問題なくラムダパラメータを使用して関数を呼び出すことができます。

ここに2つの新しいコードがあります。

最初に、指定されたテンプレートパラメータに基づいて、古いスタイルの関数ポインタ型を返すためにのみ役立つ関数宣言があります。

template <typename Func, typename Arg1>
static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr) -> decltype((*func)(*arg1)) {};

次に、テンプレート引数を取り、「getFuncType」を呼び出す予期されたラムダ型を構築する関数があります。

template <typename Func>
int myfunc(Func lambda)
{
  return myfunc<int, decltype(getFuncType<Func, int>())>(lambda);
}

正しいテンプレートパラメーターを使用して、実際の 'my​​func'を呼び出すことができます。完全なコードは次のとおりです。

template <typename R, typename T>
int myfunc(std::function<R(T)> lambda)
{
  return lambda(2);
}

template <typename Func, typename Arg1>
static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr) -> decltype((*func)(*arg1)) {};

template <typename Func>
int myfunc(Func lambda)
{
  return myfunc<int, decltype(getFuncType<Func, int>())>(lambda);
}

int r = myfunc([](int i) { return i + 1; });

'getFuncType'のオーバーロードを宣言して、ラムダパラメーターと一致させることができます。例えば:

template <typename Func, typename Arg1, typename Arg2>
static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr, Arg2* arg2 = nullptr) -> decltype((*func)(*arg1, *arg2)) {};
0
marianop