web-dev-qa-db-ja.com

C ++で述語関数を定義する正しい方法

STLアルゴリズムで使用する述語関数を記述しようとしています。述語を定義する方法は2つあると思います。

(1)以下の簡単な関数を使用します。

bool isEven(unsigned int i)   
{ return (i%2 == 0); }

std::find_if(itBegin, itEnd, isEven); 

(2)operator()関数を次のように使用します。

class checker {  
public:  
  bool operator()(unsigned int i)  
  { return (i%2 == 0); }  
}; 

std::find_if(itBegin, itEnd, checker); 

私は通常、いくつかのメンバーを含む述語オブジェクトを作成し、それらをアルゴリズムで使用するため、2番目のタイプをさらに使用します。チェッカー内に同じisEven関数を追加して述語として使用すると、エラーが発生します。
3。エラーを与える構文:

class checker { 
    public: 
       bool isEven(unsigned int i) 
       { return (i%2 == 0); }
}; 

checker c; 
std::find_if(itBegin, itEnd, c.isEven); 

コンパイル中にc.isEvenを呼び出すと、関数への未定義の参照を示すエラーが発生します。 3.がエラーを出している理由を誰かが説明できますか?また、私は述語とイテレータの基本について読むためのポインタをいただければ幸いです。

23
cppcoder

c.isEven()の型が

_bool (checker::*)(unsigned int) // member function of class
_

これはfind_if()では予期されない可能性があります。 _std::find_if_には、関数ポインター(bool (*)(unsigned int))または関数オブジェクトのいずれかが必要です。

編集:別の制約:非staticメンバー関数ポインターはclassオブジェクトによって呼び出される必要があります。あなたの場合、たとえメンバー関数を渡すことに成功したとしても、find_if()checkerオブジェクトに関する情報を持ちません。そのため、メンバー関数のポインター引数を受け入れるためにfind_if()をオーバーロードしても意味がありません。

:一般的に_c.isEven_は、メンバー関数ポインターを渡す正しい方法ではありません。 _&checker::isEven_として渡す必要があります。

5
iammilind

メンバー関数へのポインターにはインスタンスを呼び出す必要があり、メンバー関数ポインターのみを_std::find_if_に渡します(実際には構文が正しくないため、まったく機能しません。正しい構文はstd::find_if(itBegin, itEnd, &checker::isEven)でも、私が指定した理由でまだ機能しません)。

_find_if_関数は、単一のパラメーター(テストするオブジェクト)を使用して関数を呼び出すことができると想定していますが、メンバー関数を呼び出すには、インスタンスthisポインターと比較するオブジェクトの2つが実際に必要です。

operator()をオーバーロードすると、インスタンスと関数オブジェクトの両方を同時に渡すことができます。これは、これらのオブジェクトが同じになったためです。メンバー関数ポインターを使用して、1つだけを期待する関数に2つの情報を渡す必要があります。

_std::bind_(_<functional>_ヘッダーが必要)を使用してこれを行う方法があります。

_checker c;
std::find_if(itBegin, itEnd, std::bind(&checker::isEven, &c, std::placeholders::_1));
_

コンパイラが_std::bind_をサポートしていない場合は、_boost::bind_を使用することもできます。ただし、operator()をオーバーロードするだけでは、これを行う利点はありません。


もう少し詳しく説明すると、_std::find_if_は、シグネチャbool (*pred)(unsigned int)に一致する関数ポインタまたはそのように動作するものを想定しています。述語の型はテンプレートによってバインドされるため、実際には関数ポインタである必要はありません。 bool (*pred)(unsigned int)のように動作するものはすべて受け入れ可能です。これがファンクタが機能する理由です。これらは単一のパラメータで呼び出され、boolを返すことができます。

他の人が指摘したように、_checker::isEven_の型はbool (checker::*pred)(unsigned int)であり、呼び出されるにはcheckerのインスタンスが必要なため、元の関数ポインターのように動作しません。

メンバー関数へのポインターは、概念的には、追加の引数、thisポインター(たとえば、bool (*pred)(checker*, unsigned int))を取る通常の関数ポインターと見なすことができます。 std::mem_fn(&checker::isEven)を使用して、そのように呼び出すことができるラッパーを実際に生成できます(これも_<functional>_から)。 _std::find_if_がまだ好まない、1つだけではなく2つのパラメーターで呼び出す必要がある関数オブジェクトがあるため、これはまだ役に立ちません。

_std::bind_を使用すると、メンバー関数へのポインターが、thisポインターを最初の引数として取る関数であるかのように扱われます。 _std::bind_に渡される引数は、最初の引数が常に_&c_であり、2番目の引数が新しく返される関数オブジェクトの最初の引数にバインドされることを指定します。この関数オブジェクトは、1つの引数で呼び出すことができるラッパーであるため、_std::find_if_で使用できます。

_std::bind_の戻り値の型は指定されていませんが、バインドされた関数オブジェクトを別の関数に直接渡すのではなく明示的に参照する必要がある場合は、std::function<bool(unsigned int)>(この特定の場合)に変換できます。私の例でやったように。

12
Sven

_checker::isEven_は関数ではありません。メンバー関数です。また、checkerオブジェクトへの参照がなければ、非静的メンバー関数を呼び出すことはできません。したがって、関数ポインターを渡すことができる古い場所でメンバー関数を使用することはできません。メンバーポインターには特別な構文があり、呼び出すには_()_以外のものも必要です。

ファンクタがoperator()を使用するのはそのためです。これにより、メンバー関数ポインターを使用せずにオブジェクトを呼び出すことができます。

4
Nicol Bolas

私は functors (関数オブジェクト)を好みます。これは、プログラムを読みやすくし、さらに重要なことに、意図を明確に表現するためです。

これは私のお気に入りの例です:

template <typename N>
struct multiplies
{
  N operator() (const N& x, const N& y) { return x * y; }
};

vector<int> nums{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Example accumulate with transparent operator functor 
double result = accumulate(cbegin(nums), cend(nums), 1.1, multiplies<>());

注:近年は ラムダ式 がサポートされています。

// Same example with lambda expression
double result = accumulate(cbegin(nums), cend(nums), 1.1,
                            [](double x, double y) { return x * y; });
2
Marko Tunjic

この例では、呼び出し演算子(operator())を使用する必要があるとしていますが、この例では関数をisEvenと呼びます。次のように書き直してください。

class checker { 
    public: 
       bool operator()(unsigned int i) 
       { return (i%2 == 0); }
};
1
Hugh