2016-09-19 5 views
11

は、我々は次のような状況を持っている想像しない:なぜのstd ::署名copy_if述語の種類を制限しませ

struct A 
{ 
    int i; 
}; 

struct B 
{ 
    A a; 
    int other_things; 
}; 

bool predicate(const A& a) 
{ 
    return a.i > 123; 
} 

bool predicate(const B& b) 
{ 
    return predicate(b.a); 
} 

int main() 
{ 
    std::vector<A> a_source; 
    std::vector<B> b_source; 

    std::vector<A> a_target; 
    std::vector<B> b_target; 

    std::copy_if(a_source.begin(), a_source.end(), std::back_inserter(a_target), predicate); 
    std::copy_if(b_source.begin(), b_source.end(), std::back_inserter(b_target), predicate); 

    return 0; 
} 

predicate()機能の正しい過負荷がinferedすることはできませんのでstd::copy_ifへの呼び出しの両方が、コンパイルエラーが発生しますstd::copy_ifテンプレートの署名以来、コンパイラによって述語のいずれかのタイプを受け入れる:私はもっとconstraにstd::copy_if呼び出しをラップする場合はオーバーロードの解決が機能してい

template<typename _IIter, 
     typename _OIter, 
     typename _Predicate> 
_OIter copy_if(// etc... 

inedテンプレート関数:

template<typename _IIter, 
     typename _OIter, 
     typename _Predicate = bool(const typename std::iterator_traits<_IIter>::value_type&) > 
void copy_if(_IIter source_begin, 
       _IIter source_end, 
       _OIter target, 
       _Predicate pred) 
{ 
    std::copy_if(source_begin, source_end, target, pred); 
} 

私の質問は次のとおりです:STLでこれがまだこのように制約されていないのはなぜですか?私が見てきたように、_Predicate型がboolを返す関数ではなく、反復入力型を受け入れると、とにかくコンパイラエラーが発生します。だから、この制約を署名に入れておかないと、過負荷解決が機能しないのですか?

+1

制約が強すぎます( 'const'は必須ではありません。いくつかの変換が許可されます(' int'から 'bool'))。 'decltype'は正しい要件(またはConcept)を許しますが、そのメソッドはC++ 11より前に行われました。 – Jarod42

答えて

12

述語は関数である必要はありませんが、ファンクタでもある可能性があるためです。ファンクタのタイプを制限することは、operator()が定義されている限り、何でも構いません。

実は私はあなたがここで多型ファンクタにオーバーロードされた関数を変換勧め:

struct predicate { 
    bool operator()(const A& a) const 
    { 
     return a.i > 123; 
    } 

    bool operator()(const B& b) const 
    { 
     return operator()(b.a); 
    } 
} 

して、正しい過負荷が内部で選択されますつまり

std::copy_if(a_source.begin(), a_source.end(), std::back_inserter(a_target), predicate()); 
std::copy_if(b_source.begin(), b_source.end(), std::back_inserter(b_target), predicate()); 
//                      ^^ here, see the() 

、インスタンスにファンクタを呼び出しますアルゴリズム。

+0

これは非常に興味深いです。 2つの質問:(1)この場合、正しい過負荷が選択されるのはなぜですか? (2) 'operator()'を静的にすることは可能ですか? – nyarlathotep108

+2

@ nyarlathotep108、ad(1)では、テンプレートはパッケージ内で両方のオーバーロードを取得し、テンプレート内で選択が深くなり、実際には十分な情報が得られます。 Ad(2)、はい、そうすべきですが、とにかくダミーのインスタンスを作成する必要があるため、大きな違いはありません。 –

+0

そして、実際にはラムダは関数オブジェクトの観点から定義されており、非キャプチャラムダだけが定義された関数ポインタへの変換を持っています(キャプチャされた値をどこかに保存しなければなりません!) – JohannesD

4

この問題は、アルゴリズムの述語に影響するだけではありません。これは、テンプレート型の控除によってオーバーロードされた関数が推定されるあらゆる場所で発生します。オーバーロード解決の前にテンプレート型の控除が行われるため、コンパイラはあいまいさを解消するコンテキスト情報を欠いています。それは、結合し、ラムダ、ファンクタ、mem_fn秒というように型変換を考慮引数に取ると返す必要があるだろうと

正しく書き込まれた制約は、恐ろしく複雑になります。

曖昧さ(IMHO)を解決する簡単な方法は、ラムダを介して述語を呼び出すことです。

std::copy_if(a_source.begin(), a_source.end(), 
     std::back_inserter(a_target), 
     [](auto&& x){ return predicate(std::forward<decltype(x)>(x)); }); 

これは、テンプレートタイプの控除後まで、オーバーロードの解決を防ぎます。

struct predicate_caller 
{ 
    template<class T> 
    decltype(auto) operator()(T&& t) const 
    { 
    return predicate(std::forward<T>(t)); 
    } 
}; 

などのように呼び出します:

C++ 14

次に手-ロールに同じラムダをアップグレードするために、私は拒否し(または私の上司が拒否した)場合はどう

std::copy_if(b_source.begin(), b_source.end(), 
      std::back_inserter(b_target), 
      predicate_caller()); 
+0

これはC++ 14の汎用ラムダでのみ動作します。これは、あなたのローカルコンパイラでまだ利用できないほど新しいものです。 –

+0

@JanHudec私は、C++への無痛化アップグレードがないC++ 11コンパイラは考えられません。私の見解では、C++ 11での滞在はそれ自体が反パターンです。ただし、テンプレート付きコール演算子を持つ関数オブジェクトで十分です。 –

+0

@JanHudec手巻きのテンプレートファンクターは、そこにあるすべてのダイハードに用意されています。 –

関連する問題