40

次のコードを考えると、あいまいさの背後にある理由は何ですか?それを回避することはできますか、それとも(迷惑な)明示的なキャストを維持する必要がありますか?

#include <functional>

using namespace std;

int a(const function<int ()>& f)
{
    return f();
}

int a(const function<int (int)>& f)
{
    return f(0);
}

int x() { return 22; }

int y(int) { return 44; }

int main()
{
    a(x);  // Call is ambiguous.
    a(y);  // Call is ambiguous.

    a((function<int ()>)x);    // Works.
    a((function<int (int)>)y); // Works.

    return 0;
}

a()興味深いことに、パラメーターを使用して関数をコメントアウトし、mainfunction<int ()>を呼び出すと、使用可能な唯一の関数の型と引数a(x)の型が一致しないため、コンパイルが正しく失敗します。その場合にコンパイラが失敗した場合、2つの関数が存在するときにあいまいさが生じるのはなぜですか?xfunction<int (int)>a()a()

VS2010とg++v。4.5で試しました。どちらもまったく同じあいまいさを与えてくれます。

4

4 に答える 4

42

問題は、との両方function<int()>function<int(int)>同じ関数から構築可能であるということです。std::functionVS2010でのコンストラクター宣言は次のようになります。

template<class _Fx>
function(_Fx _Func, typename _Not_integral<!_Is_integral<_Fx>::value, int>::_Type = 0);

SFINAEの部分を無視すると、ほとんど何からでも構築できます。型消去
std::/boost::functionと呼ばれる手法を使用して、呼び出されたときに署名を満たす限り、任意のオブジェクト/関数を渡すことができます。それからの1つの欠点は、コンストラクターではなく、署名が望むように呼び出すことができないオブジェクトを提供するときに、実装の最も深い部分(保存された関数が呼び出される場所)でエラーが発生することです。


この小さなクラスで問題を説明できます。

template<class Signature>
class myfunc{
public:
    template<class Func>
    myfunc(Func a_func){
        // ...
    }
};

これで、コンパイラがオーバーロードセットの有効な関数を検索するときに、完全に適合する関数が存在しない場合、引数を変換しようとします。変換は、関数のパラメーターのコンストラクターを介して、または関数に与えられた引数の変換演算子を介して行うことができます。私たちの場合、それは前者です。
コンパイラは、の最初のオーバーロードを試行しaます。それを実行可能にするには、変換を行う必要があります。をに変換するためint(*)()myfunc<int()>、のコンストラクタを試しmyfuncます。何でも取るテンプレートなので、変換は自然に成功します。
次に、2番目のオーバーロードで同じことを試みます。コンストラクターは同じであり、与えられたものをすべて取得しているため、変換も機能します。
オーバーロードセットに2つの関数が残っているため、コンパイラは悲しいパンダであり、何をすべきかわからないため、呼び出しがあいまいであると単純に言います。


したがって、最終的にSignature、テンプレートの一部は、宣言/定義を作成するときは型に属しますが、オブジェクトを作成するときはそうではありません。


編集
タイトルの質問に答えることにすべての注意を払って、私はあなたの2番目の質問を完全に忘れました。:(

それを回避することはできますか、それとも(迷惑な)明示的なキャストを維持する必要がありますか?

Afaik、3つのオプションがあります。

  • キャストを維持する
  • 適切なタイプのfunctionオブジェクトを作成し、それを渡します

    function<int()> fx = x; function<int(int)> fy = y; a(fx); a(fy);

  • 関数内の面倒なキャストを非表示にし、TMPを使用して適切な署名を取得します

TMP(テンプレートメタプログラミング)バージョンは非常に冗長でボイラープレートコードを備えていますが、キャストをクライアントから隠します。サンプルバージョンはここにあります。これは、関数ポインター型に部分的に特化したメタ関数に依存していget_signatureます(C ++でパターンマッチングがどのように機能するかについての良い例を提供します)。

template<class F>
struct get_signature;

template<class R>
struct get_signature<R(*)()>{
  typedef R type();
};

template<class R, class A1>
struct get_signature<R(*)(A1)>{
  typedef R type(A1);
};

もちろん、これはサポートしたい引数の数に合わせて拡張する必要がありますが、それは一度実行されてから"get_signature.h"ヘッダーに埋め込まれます。:)

私が検討しているがすぐに破棄されるもう1つのオプションは、TMPバージョンよりもさらに多くのボイラープレートコードを導入するSFINAEでした。

だから、ええ、それは私が知っているオプションです。それらの1つがあなたのために働くことを願っています。:)

于 2011-05-09T00:26:23.620 に答える
11

私はこの質問が何度も出てくるのを見てきました。 libc ++は、このコードをあいまいさなしに(適合拡張として)コンパイルするようになりました。

期限切れの更新

この「拡張機能」は十分に人気があり、C ++ 14で標準化されました(ただし、私はその仕事を遂行する責任はありませんでした)。

後から考えると、この拡張機能は正確にはわかりませんでした。今月初め(2015-05-09)、委員会はLWG問題2420で投票しました。これにより、 Callableの定義が効果的に変更され、リターンタイプがある場合std::functionvoid、ラップされたファンクターのリターンタイプが無視されますが、それ以外の場合はCallableと見なされますCallableではないと見なすのではなく、他のすべてが一致します。

関連するリターンタイプは一貫してであるため、このC++14以降の調整はこの特定の例に影響を与えませんint

于 2011-05-31T23:16:32.277 に答える
4

std::functionコンストラクターパラメーターの呼び出し可能性をチェックするクラスをラップする方法の例を次に示します。

template<typename> struct check_function;
template<typename R, typename... Args>
struct check_function<R(Args...)>: public std::function<R(Args...)> {
    template<typename T,
        class = typename std::enable_if<
            std::is_same<R, void>::value
            || std::is_convertible<
                decltype(std::declval<T>()(std::declval<Args>()...)),
                R>::value>::type>
        check_function(T &&t): std::function<R(Args...)>(std::forward<T>(t)) { }
};

このように使用します:

int a(check_function<int ()> f) { return f(); }
int a(check_function<int (int)> f) { return f(0); }

int x() { return 22; }
int y(int) { return 44; }

int main() {
    a(x);
    a(y);
}

これは、変換可能な引数(および戻り値)型を同等として扱うため、関数シグネチャのオーバーロードとはまったく同じではないことに注意してください。正確なオーバーロードの場合、これは機能するはずです。

template<typename> struct check_function_exact;
template<typename R, typename... Args>
struct check_function_exact<R(Args...)>: public std::function<R(Args...)> {
    template<typename T,
        class = typename std::enable_if<
            std::is_convertible<T, R(*)(Args...)>::value>::type>
        check_function_exact(T &&t): std::function<R(Args...)>(std::forward<T>(t)) { }
};
于 2012-08-21T12:08:46.617 に答える
2

std::function<T>任意の型(つまり、以外のものT)をとる変換コンストラクターがあります。確かに、この場合、そのctorは型の不一致エラーを引き起こしますが、コンパイラーはそれほど遠くまでは到達しません。ctorが存在するという理由だけで、呼び出しはあいまいです。

于 2011-05-09T00:36:45.390 に答える