12

コンテナーを反復処理し、各要素をフィルター処理のために述語に渡す関数があります。この関数のオーバーロードは、各要素のインデックスも述語に渡します。

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);

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

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ますか?

4

2 に答える 2

12

オーバーロードのあいまいさが予想されます。

std::functionには、任意の引数を受け入れる変換コンストラクター テンプレートがあります。コンストラクター テンプレートがインスタンス化された後でのみ、コンパイラーは引数を拒否することを決定できます。

最初の例と 2 番目の例の両方で、指定されていないラムダ型を各型に変換するには、ユーザー定義の変換が必要ですstd::function。どちらの変換も優れていない (どちらもユーザー定義の変換である) ため、コンパイラはオーバーロードのあいまいさを報告します。

std::function3 番目の例 (動作する例) では、コンストラクター テンプレートが使用されていないため、あいまいさはありません。代わりに、そのコピー コンストラクターが使用されます (そして、他のすべての条件が同じであれば、テンプレートよりも非テンプレートが優先されます)。

于 2012-02-18T08:21:45.867 に答える
5

std::functionバイナリ区切りで使用されますが、ファンクターの汎用パラメーターとしては使用されません。あなたが発見したように、その変換コンストラクターはオーバーロードの解決とうまく相互作用しません (そして、ラムダ式とは何の関係もありません)。DoSomethingIfすでにテンプレートであるため、一般化されたファンクターを受け入れる正規のソリューションに問題はありません。

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

ただし、お気づきかもしれませんが、このバージョンはオーバーロードできず、述語として何でも受け入れるだけ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はできません。別の方法として、ここにある条件を繰り返すこともできます。これはコード内のドキュメントとして機能しますが、機能自体は向上しません。)static_assertfalsestd::enable_if

残ってis_callable<Functor, Signature>いるのは、実際には標準の特性ではないため、どこで を見つけるかだけです。これまでに SFINAE テストを作成したことがあれば、比較的簡単に実装できますが、voidリターンに部分的に特化する必要があるため、少し面倒です。この回答はそのままでは十分に長いため、ここでは専門分野を説明しません。

このソリューションが強力だが冗長すぎると感じた場合は、おそらく概念を楽しむことができます:)

于 2012-02-18T13:56:37.733 に答える