web-dev-qa-db-ja.com

std :: functionがオーバーロードされた関数を区別できない

_std::function_がオーバーロードされた関数を区別できない理由を理解しようとしています。

_#include <functional>

void add(int,int){}

class A {};

void add (A, A){}

int main(){
        std::function <void(int, int)> func = add;
}
_

上記のコードでは、function<void(int, int)>はこれらの関数の1つにのみ一致しますが、失敗します。これはなぜですか?実際の関数へのラムダまたは関数ポインターを使用して、関数ポインターを関数に格納することで、これを回避できることを知っています。しかし、なぜこれが失敗するのですか?選択したい機能のコンテキストが明確ではないですか?この場合、テンプレートマッチングが失敗する理由を理解できないため、これが失敗する理由を教えてください。

このためにclangで発生するコンパイラエラーは次のとおりです。

_test.cpp:10:33: error: no viable conversion from '<overloaded function type>' to
      'std::function<void (int, int)>'
        std::function <void(int, int)> func = add;
                                       ^      ~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1266:31: note: 
      candidate constructor not viable: no overload of 'add' matching
      'std::__1::nullptr_t' for 1st argument
    _LIBCPP_INLINE_VISIBILITY function(nullptr_t) : __f_(0) {}
                              ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1267:5: note: 
      candidate constructor not viable: no overload of 'add' matching 'const
      std::__1::function<void (int, int)> &' for 1st argument
    function(const function&);
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1269:7: note: 
      candidate template ignored: couldn't infer template argument '_Fp'
      function(_Fp,
      ^
1 error generated.
_

編集-MSaltersの回答に加えて、私はこのフォーラムを検索して、これが失敗する正確な理由を見つけました。私はこの post でNawazの返答から答えを得ました。

私は彼の答えからコピーをここに貼り付けました:

_    int test(const std::string&) {
        return 0;
    }

    int test(const std::string*) {
        return 0;
    }

    typedef int (*funtype)(const std::string&);

    funtype fun = test; //no cast required now!
    std::function<int(const std::string&)> func = fun; //no cast!
_

では、なぜstd::function<int(const std::string&)>が上記の_funtype fun = test_のように機能しないのでしょうか。

その答えは、_std::function_は任意のオブジェクトで初期化できるためですそのコンストラクタはテンプレート化されているため、_std::function_に渡したテンプレート引数とは無関係です

32
MS Srikkanth

どの関数を選択するかは私たちには明らかですが、コンパイラーはC++の規則に従う必要があり、賢明なロジックの飛躍を使用しないでください(または、このような単純な場合のように、それほど賢明な飛躍ではありません!)

_std::function_の関連するコンストラクタは次のとおりです。

_template<class F> function(F f);
_

これは、anyタイプを受け入れるテンプレートです。

C++ 14標準はテンプレートを制約します( LWG DR 2132 以降)。

引数タイプ_ArgTypes..._のfがCallable(20.9.12.2)で、タイプRを返さない限り、オーバーロードの解決に参加しないものとします。

つまり、Functorが_std::function_(例ではvoid(int, int))の呼び出し署名と互換性がある場合にのみ、コンパイラーはコンストラクターの呼び出しを許可します。理論的には、void add(A, A)は実行可能な引数ではないため、「明らかに」void add(int, int)を使用するつもりでした。

ただし、コンパイラは、「f is callable for argument types ...」制約をテストできません。 fは、void add(int, int)void add(A, A)beforeの間で明確化されている必要があることを意味しますこれらの関数の1つを拒否できるようにする制約を適用できます。

したがって、鶏と卵の状況があります。これは、残念ながら、使用するaddのオーバーロードを正確に指定してコンパイラを支援する必要があることを意味し、その後コンパイラーは制約を適用でき、(むしろ冗長に)コンストラクターに受け入れ可能な引数であると判断できます。

このような場合allにオーバーロードされた関数が制約に対してテストされるようにC++を変更できると考えられます(したがって、知る必要はありません)それをテストする前にどちらをテストするか)、そして実行可能なのが1つだけの場合はその1つを使用しますが、C++の動作はそうではありません。

22
Jonathan Wakely

あなたが何を望んでいるかは明らかですが、問題は_std::function_が_&add_のオーバーロード解決に影響を与えないことです。生の関数ポインタ(void (*func)(int,int) = &add)を初期化する場合は、機能します。これは、関数ポインターの初期化が、オーバーロードの解決が行われるコンテキストであるためです。ターゲットのタイプは正確にわかっています。しかし、_std::function_は、呼び出し可能なほとんどすべての引数を取ります。引数を受け入れる柔軟性は、_&add_でオーバーロードの解決を実行できないことを意味します。 addの複数のオーバーロードが適している場合があります。

明示的なキャスト、つまりstatic_cast<void(*)(int, int)> (&add)が機能します。

これをtemplate<typename F> std::function<F> make_function(F*)でラップすると、auto func = make_function<int(int,int)> (&add)を記述できます。

17
MSalters

試してください:

_std::function <void(int, int)> func = static_cast<void(*)(int, int)> (add);
_

void add(A, A)void add(int, int)へのアドレスは明らかに異なります。関数を名前で指す場合、コンパイラーがどの関数アドレスが必要かを知ることはほとんど不可能です。 void(int, int)ここにヒントはありません。

4
W.F.

これに対処する別の方法は、C++ 14の汎用ラムダを使用することです。

int main() {
    std::function <void(int, int)> func = [](auto &&... args) { add(std::forward<decltype(args)>(args)...);
}

これにより、曖昧さなく問題を解決するラムダ関数が作成されます。私は議論を転送しませんでした、

1
Germán Diago