2012-02-18 19 views
12

私は、コンテナを反復処理し、各要素をフィルタリングのための述語に渡す関数を持っています。この関数のオーバーロードは、各要素のインデックスも述語に渡します。C++ラムダは正しくオーバーロードされた関数を選択しませんか?

template<typename TContainer> 
void DoSomethingIf(TContainer &c, std::function<bool (const typename TContainer::const_reference)> predicate); 

template<typename TContainer> 
void DoSomethingIf(TContainer &c, std::function<bool (const typename TContainer::const_reference, int)> predicate); 

私は成功するのstd ::関数オブジェクトを使用しながら、裸のラムダで、これらの機能のいずれかを呼び出そうとすると、VC11でコンパイルエラーが発生しますことを発見した:

void foo() 
{ 
    std::vector<int> v; 

    // fails 
    DoSomethingIf(v, [](const int &x) { return x == 0; }); 

    // also fails 
    auto lambda = [](const int &x) { return x == 0; }; 
    DoSomethingIf(v, lambda); 

    // success! 
    std::function<bool (const int &)> fn = [](const int &x) { return x == 0; }; 
    DoSomethingIf(v, fn); 
} 

1>c:\users\moswald\test.cpp(15): error C2668: 'DoSomethingIf' : ambiguous call to overloaded function 
1>   c:\users\moswald\test.cpp(8): could be 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)' 
1>   with 
1>   [ 
1>    _Ty=int, 
1>    TContainer=std::vector<int>, 
1>    _Fty=bool (const int &,int) 
1>   ] 
1>   c:\users\moswald\test.cpp(5): or  'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)' 
1>   with 
1>   [ 
1>    _Ty=int, 
1>    TContainer=std::vector<int>, 
1>    _Fty=bool (const int &) 
1>   ] 
1>   while trying to match the argument list '(std::vector<_Ty>, foo::<lambda_8EADDE04A8D35A3C>)' 
1>   with 
1>   [ 
1>    _Ty=int 
1>   ] 
1>c:\users\moswald\test.cpp(19): error C2668: 'DoSomethingIf' : ambiguous call to overloaded function 
1>   c:\users\moswald\test.cpp(8): could be 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)' 
1>   with 
1>   [ 
1>    _Ty=int, 
1>    TContainer=std::vector<int>, 
1>    _Fty=bool (const int &,int) 
1>   ] 
1>   c:\users\moswald\test.cpp(5): or  'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)' 
1>   with 
1>   [ 
1>    _Ty=int, 
1>    TContainer=std::vector<int>, 
1>    _Fty=bool (const int &) 
1>   ] 
1>   while trying to match the argument list '(std::vector<_Ty>, foo::<lambda_8EADDE04A8D35A3D>)' 
1>   with 
1>   [ 
1>    _Ty=int 
1>   ] 

これは期待されていますか?これらの関数をオーバーロードする別の方法は、過負荷の曖昧さが期待されている?「DoSomethingIfWithIndex」に1をリネームの短い(

答えて

12

あります。

std::functionは、任意の引数を受け取り変換コンストラクタテンプレートを持っている。唯一のコンストラクタテンプレート後

不特定のラムダ型をstd::function型に変換するには、ユーザー定義の変換が必要です。どちらの変換も優れていません(これは、コンパイラが引数を拒否することをコンパイラが判断できるかどうかを示します)。両方ともユーザー定義の変換です)、コンパイラはオーバーロードambiguiをレポートしますty。

第3の例(動作するもの)では、std::functionコンストラクタテンプレートが使用されていないため、あいまいさはありません。その代わりに、そのコピーコンストラクタが使用されます(他のすべてのものが等しい場合は、テンプレート以外には非テンプレートが優先されます)。

+0

は感覚が、期待はずれにします。これは、異なるラムダを通して過負荷関数を選択する方法が決してないことを意味するように見えます。 – moswald

+0

'std :: function'を使用していません。呼び出し可能な特性を使用したり、キャプチャのないラムダの関数ポインタの減衰に頼ることはできますが、一般的にはバットの痛みです。 –

+0

このような問題を解決するために、呼び出し可能な特性を使用するにはどうすればよいでしょうか?可能であれば、自由な関数/ lambdaだけでなく、署名に合った関数オブジェクトを渡すことができるようにしたいと思います。 – moswald

5

std::functionは、バイナリデリミタでは使用されますが、ではなく、ファンクタの一般的なパラメータとしてです。ちょうどあなたが発見したように、変換コンストラクタは過負荷解決に深刻に作用します(ラムダ式とは関係ありません)。あなたがが気づくこととして、このバージョンが過負荷にすることができない、

template<typename TContainer, typename Predicate> 
void DoSomethingIf(TContainer& c, Predicate&& predicate); 

と単に何かを受け入れます:DoSomethingIfすでにテンプレートですので、私は受け入れて一般ファンクタの標準溶液を用いて、問題が表示されません述語として、さらにint。オーバーロードを簡単にいつものように、SFINAEにより解決される:これはまだ私達の条件を満たしていない誰かが述語(または本当に何を)渡す場合という厄介な問題がある

template< 
    typename Container 
    , typename Predicate 
    , typename = typename std::enable_if< 
     is_callable<Predicate, bool(typename Container::const_reference)>::value 
    >::type 
> 
void 
DoSomethingIf(Container& container, Predicate&& predicate); 

template< 
    typename Container 
    , typename Predicate 
    , typename = typename std::enable_if< 
     is_callable<Predicate, bool(typename Container::const_reference, int)>::value 
    >::type 
    // dummy parameter to disambiguate this definition from the previous one 
    , typename = void 
> 
void 
DoSomethingIf(Container& container, Predicate&& predicate); 

、我々が見つかりました。「は一致する関数を取得していません'エラー(過負荷解決の失敗)ではなく、有用なエラーです。あなたはそれを解決したい場合は、「キャッチオール」過負荷追加することができます。

template< 
    typename Container 
    , typename Predicate 
    , typename = typename std::enable_if< 
     !is_callable<Predicate, bool(typename Container::const_reference)>::value 
     && !is_callable<Predicate, bool(typename Container::const_reference, int)>::value 
    >::type 
    // more dummies 
    , typename = void, typename = void 
> 
void DoSomethingIf(Container&, Predicate&&) 
{ static_assert(dependent_false_type<Container>::value, 
    "Put useful error message here"); } 

を(dependent_false_typeがちょうどstd::false_typeから継承タイプを例えばする必要があり、単にfalseまたはその上の私たちがすることができませんstatic_assertになりますテンプレートがインスタンス化されたときだけでなく、内部にあるstd::enable_ifの条件を繰り返すこともできます。これはコード内のドキュメントとして多少機能しますが、機能自体は改善されません。 )

残っているのはどこですか?is_callable<Functor, Signature>それは実際には標準的な形質ではないからです。前にSFINAEテストを書いたことがあれば実装するのは比較的簡単ですが、部分的に専門化してvoidを返さなければならないので少し面倒です。私は、この答えが十分に長いので、ここに専門を置くわけではありません。

あなたは強力ですが、あまりにも冗長この解決策を見つけた場合は、おそらくあなたは概念を楽しむと思います:)

+0

ありがとうございます。ええ、私は 'type_traits'ヘッダを見て、そこに' is_callable'に相当するテンプレートの組み合わせを見つけようとしました。この全部がもともと私のC++スキルを鋭く保つための練習であったので、私は確かに自分自身を書こうと思っています。 – moswald

+0

これは側面ですが、enable-if条件によってのみ異なる機能テンプレートを区別するために追加のダミーテンプレートパラメータを導入する必要を避ける方法の1つは、「typename enable_if :: type * = 0」パラメータを使用することです。 'typename = typename enable_if :: type'パラメータです。条件が他のテンプレートパラメータに依存する限り、コンパイラは 'enable_if :: type'と' enable_if :: type'を潜在的に異なる型とみなし、したがって2つの関数テンプレートを異なるものにする必要があります。 – HighCommander4

関連する問題