62

boost::functionこの質問はとにも当てはまりますstd::tr1::function

std::functionは同等ではありません:

#include <functional>
void foo() { }

int main() {
    std::function<void()> f(foo), g(foo);
    bool are_equal(f == g); // Error:  f and g are not equality comparable
}

C++11 では、オーバーロードoperator==operator!=オーバーロードは存在しません。初期の C++11 ドラフトでは、オーバーロードはコメントで削除済みとして宣言されていました (N3092 §20.8.14.2):

// deleted overloads close possible hole in the type system

「型システムの穴の可能性」が何であるかは述べていません。TR1 と Boost では、オーバーロードは宣言されていますが、定義されていません。TR1 仕様のコメント (N1836 §3.7.2.6):

これらのメンバー関数は未定義のままにします。

[注:ブール型の変換は抜け穴を開き、2 つの関数インスタンスを==orで比較できます!=。これらの未定義voidの演算子は抜け穴を塞ぎ、コンパイル時のエラーを確実にします。—終わりのメモ]

「抜け穴」についての私の理解は、bool変換関数がある場合、その変換は等値比較 (およびその他の状況) で使用される可能性があるということです。

struct S {
    operator bool() { return false; }
};

int main() {
    S a, b;
    bool are_equal(a == b); // Uses operator bool on a and b!  Oh no!
}

C++03 の safe-bool イディオムと C++11 の明示的な変換関数の使用は、この「抜け穴」を回避するために使用されたという印象を受けました。Boost と TR1 は両方とも safe-bool イディオムを使用しfunction、C++11 はbool変換関数を明示的にします。

両方を持つクラスの例として、std::shared_ptr両方が明示的なbool変換関数を持ち、等値比較可能です。

なぜstd::function平等は比較できないのですか?「型システムの穴の可能性」とは何ですか? とどう違うのstd::shared_ptr

4

7 に答える 7

37

なぜstd::function平等は比較できないのですか?

std::function任意の呼び出し可能な型のラッパーであるため、等価比較を実装するには、すべての呼び出し可能な型が等価比較可能であることを要求する必要があり、関数オブジェクトを実装するすべての人に負担がかかります。それでも、(たとえば) 異なる順序で引数をバインドすることによって構築された場合、同等の関数は等しくないと比較されるため、同等の狭い概念が得られます。一般的なケースで同等性をテストすることは不可能だと思います。

「型システムの穴の可能性」とは何ですか?

これは、演算子を削除する方が簡単であり、以前に発見されていないコーナーケースで不要な暗黙の変換が発生する可能性がないことを証明するよりも、演算子を使用しても有効なコードが得られないことを確実に知っていることを意味すると思います。

とどう違うのstd::shared_ptr

std::shared_ptr明確に定義された等価セマンティクスを持っています。2 つのポインターが等しいのは、それらが両方とも空であるか、または両方とも空ではなく同じオブジェクトを指している場合のみです。

于 2010-09-02T18:45:05.107 に答える
23

私は間違っているかもしれませんが、std::functionオブジェクトの平等は残念ながら一般的な意味では解決できないと思います。例えば:

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <cstdio>

void f() {
    printf("hello\n");
}

int main() {
    boost::function<void()> f1 = f;
    boost::function<void()> f2 = boost::bind(f);

    f1();
    f2();
}

f1等しいですf2か?さまざまな方法で互いにラップするだけの任意の数の関数オブジェクトを追加すると、最終的にはf...への呼び出しになります。

于 2010-09-02T18:21:14.063 に答える
13

なぜstd::function平等は比較できないのですか?

主な理由は、もしそうなら、たとえ等価比較が実行されなくても、非等価比較型では使用できなかったからだと思います。

つまり、比較を実行するコードは、早い段階でインスタンス化する必要がありますstd::function。たとえば、コンストラクターまたは代入演算子のいずれかで、呼び出し可能なオブジェクトが に格納された時点です。

このような制限は、アプリケーションの範囲を大幅に狭め、明らかに「汎用ポリモーフィック関数ラッパー」には受け入れられません。


a を呼び出し可能なオブジェクトと比較するboost::functionことは可能であることに注意することが重要です(ただし、別の とは比較できませんboost::function) 。

関数オブジェクト ラッパーは、ラッパー内に格納できる任意の関数オブジェクトを介して==、またはそれに対して比較できます。!=

このような比較を実行する関数は、既知のオペランド型に基づいて比較ポイントでインスタンス化されるため、これが可能です。

さらに、std::function同様の比較を実行するために使用できるtargetテンプレートメンバー関数があります。実際、boost::function比較演算子はメンバー関数の観点から実装されていtargetます。

したがって、 の実装を妨げる技術的な障壁はありませんfunction_comparable


答えの中には、一般的な「一般的に不可能」なパターンがあります。

  • それでも、(たとえば) 異なる順序で引数をバインドすることによって構築された場合、同等の関数は等しくないと比較されるため、同等の狭い概念が得られます。一般的なケースで同等性をテストすることは不可能だと思います。

  • 私は間違っているかもしれませんが、std::functionオブジェクトの平等は残念ながら一般的な意味では解決できないと思います。

  • チューリングマシンの等価性は決定できないからです。2 つの異なる関数オブジェクトが与えられた場合、それらが同じ関数を計算するかどうかを判断できない可能性があります。[その回答は削除されました]

私はこれに完全に同意しませんstd::function。比較自体を実行するのは仕事ではありません。その仕事は、リクエストを基になるオブジェクトとの比較にリダイレクトすることだけです-それだけです。

基になるオブジェクト タイプで比較が定義されていない場合は、コンパイル エラーになります。いずれにせよ、std::function比較アルゴリズムを推測する必要はありません。

基礎となるオブジェクト型が比較を定義しているが、それが正しく動作しないか、異常なセマンティクスを持っている場合、std::functionそれ自体の問題ではなく、基礎となる型の問題です。


function_comparableに基づいて実装することが可能std::functionです。

概念実証は次のとおりです。

template<typename Callback,typename Function> inline
bool func_compare(const Function &lhs,const Function &rhs)
{
    typedef typename conditional
    <
        is_function<Callback>::value,
        typename add_pointer<Callback>::type,
        Callback
    >::type request_type;

    if (const request_type *lhs_internal = lhs.template target<request_type>())
        if (const request_type *rhs_internal = rhs.template target<request_type>())
            return *rhs_internal == *lhs_internal;
    return false;
}

#if USE_VARIADIC_TEMPLATES
    #define FUNC_SIG_TYPES typename ...Args
    #define FUNC_SIG_TYPES_PASS Args...
#else
    #define FUNC_SIG_TYPES typename function_signature
    #define FUNC_SIG_TYPES_PASS function_signature
#endif

template<FUNC_SIG_TYPES>
struct function_comparable: function<FUNC_SIG_TYPES_PASS>
{
    typedef function<FUNC_SIG_TYPES_PASS> Function;
    bool (*type_holder)(const Function &,const Function &);
public:
    function_comparable() {}
    template<typename Func> function_comparable(Func f)
        : Function(f), type_holder(func_compare<Func,Function>)
    {
    }
    template<typename Func> function_comparable &operator=(Func f)
    {
        Function::operator=(f);
        type_holder=func_compare<Func,Function>;
        return *this;
    }
    friend bool operator==(const Function &lhs,const function_comparable &rhs)
    {
        return rhs.type_holder(lhs,rhs);
    }
    friend bool operator==(const function_comparable &lhs,const Function &rhs)
    {
        return rhs==lhs;
    }
    friend void swap(function_comparable &lhs,function_comparable &rhs)// noexcept
    {
        lhs.swap(rhs);
        lhs.type_holder.swap(rhs.type_holder);
    }
};

優れたプロパティがあります-function_comparable比較することもできstd::functionます。

たとえば、 s のベクトルがありstd::function、ユーザーregister_callbackunregister_callback関数を提供したいとしましょう。の使用は、パラメータfunction_comparableにのみ必要です:unregister_callback

void register_callback(std::function<function_signature> callback);
void unregister_callback(function_comparable<function_signature> callback);

Ideoneでのライブ デモ

デモのソース コード:

//             Copyright Evgeny Panasyuk 2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include <type_traits>
#include <functional>
#include <algorithm>
#include <stdexcept>
#include <iostream>
#include <typeinfo>
#include <utility>
#include <ostream>
#include <vector>
#include <string>

using namespace std;

// _____________________________Implementation__________________________________________

#define USE_VARIADIC_TEMPLATES 0

template<typename Callback,typename Function> inline
bool func_compare(const Function &lhs,const Function &rhs)
{
    typedef typename conditional
    <
        is_function<Callback>::value,
        typename add_pointer<Callback>::type,
        Callback
    >::type request_type;

    if (const request_type *lhs_internal = lhs.template target<request_type>())
        if (const request_type *rhs_internal = rhs.template target<request_type>())
            return *rhs_internal == *lhs_internal;
    return false;
}

#if USE_VARIADIC_TEMPLATES
    #define FUNC_SIG_TYPES typename ...Args
    #define FUNC_SIG_TYPES_PASS Args...
#else
    #define FUNC_SIG_TYPES typename function_signature
    #define FUNC_SIG_TYPES_PASS function_signature
#endif

template<FUNC_SIG_TYPES>
struct function_comparable: function<FUNC_SIG_TYPES_PASS>
{
    typedef function<FUNC_SIG_TYPES_PASS> Function;
    bool (*type_holder)(const Function &,const Function &);
public:
    function_comparable() {}
    template<typename Func> function_comparable(Func f)
        : Function(f), type_holder(func_compare<Func,Function>)
    {
    }
    template<typename Func> function_comparable &operator=(Func f)
    {
        Function::operator=(f);
        type_holder=func_compare<Func,Function>;
        return *this;
    }
    friend bool operator==(const Function &lhs,const function_comparable &rhs)
    {
        return rhs.type_holder(lhs,rhs);
    }
    friend bool operator==(const function_comparable &lhs,const Function &rhs)
    {
        return rhs==lhs;
    }
    // ...
    friend void swap(function_comparable &lhs,function_comparable &rhs)// noexcept
    {
        lhs.swap(rhs);
        lhs.type_holder.swap(rhs.type_holder);
    }
};

// ________________________________Example______________________________________________

typedef void (function_signature)();

void func1()
{
    cout << "func1" << endl;
}

void func3()
{
    cout << "func3" << endl;
}

class func2
{
    int data;
public:
    explicit func2(int n) : data(n) {}
    friend bool operator==(const func2 &lhs,const func2 &rhs)
    {
        return lhs.data==rhs.data;
    }
    void operator()()
    {
        cout << "func2, data=" << data << endl;
    }
};
struct Caller
{
    template<typename Func>
    void operator()(Func f)
    {
        f();
    }
};
class Callbacks
{
    vector<function<function_signature>> v;
public:
    void register_callback_comparator(function_comparable<function_signature> callback)
    {
        v.push_back(callback);
    }
    void register_callback(function<function_signature> callback)
    {
        v.push_back(callback);
    }
    void unregister_callback(function_comparable<function_signature> callback)
    {
        auto it=find(v.begin(),v.end(),callback);
        if(it!=v.end())
            v.erase(it);
        else
            throw runtime_error("not found");
    }
    void call_all()
    {
        for_each(v.begin(),v.end(),Caller());
        cout << string(16,'_') << endl;
    }
};

int main()
{
    Callbacks cb;
    function_comparable<function_signature> f;
    f=func1;
    cb.register_callback_comparator(f);

    cb.register_callback(func2(1));
    cb.register_callback(func2(2));
    cb.register_callback(func3);
    cb.call_all();

    cb.unregister_callback(func2(2));
    cb.call_all();
    cb.unregister_callback(func1);
    cb.call_all();
}

出力は次のとおりです。

func1
func2, data=1
func2, data=2
func3
________________
func1
func2, data=1
func3
________________
func2, data=1
func3
________________

PS の助けを借りてstd::type_index、クラスに似たものを実装することができるようです。function_comparableこれは、順序付け (つまりstd::less) またはハッシュもサポートします。異なるタイプ間の順序付けだけでなく、同じタイプ内での順序付けも可能です (これには のようなタイプからのサポートが必要ですLessThanComparable)。

于 2012-10-27T23:23:50.297 に答える
6

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#1240によると:

ここでの先頭のコメントはstd::function、N1402 で導入された の歴史の一部です。その間、明示的な変換関数は存在せず、"safe-bool" イディオム (メンバーへのポインターに基づく) が一般的な手法でした。このイディオムの唯一の欠点は、2 つのオブジェクトf1f2type が与えられstd::functionた場合、式が

f1 == f2;

operator==メンバーへのポインターの組み込みが単一のユーザー定義の変換後に考慮されたという理由だけで、整形式でした。これを修正するために、未定義の比較関数のオーバーロード セットが追加されました。これにより、オーバーロードの解決では、最終的にリンケージ エラーになるものが優先されます。削除された関数の新しい言語機能により、この問題を修正するためのより優れた診断メカニズムが提供されました。

C++11 では、削除された関数は明示的な変換演算子の導入により不要と見なされるため、C++11 では削除される可能性があります。

この問題の中心点は、明示的な変換による safe-bool イディオムの置き換えによりbool、元の「型システムの穴」がもはや存在しないため、コメントが間違っており、余分な関数定義を削除する必要があることです。同じように。

オブジェクトを比較できない理由についてはstd::function、おそらく、グローバル/静的関数、メンバー関数、ファンクターなどを保持できる可能性があり、それを行うとstd::function、基になる型に関する情報の一部が「消去」されるためです。そのため、等値演算子を実装することはおそらく実現不可能です。

于 2010-09-02T18:17:24.510 に答える
4

実際、ターゲットを比較できます。比較から何を求めているかによって機能する場合があります。

ここに不等式のコードがありますが、それがどのように機能するかを見ることができます:

template <class Function>
struct Comparator
{
    bool operator()(const Function& f1, const Function& f2) const
    {
        auto ptr1 = f1.target<Function>();
        auto ptr2 = f2.target<Function>();
        return ptr1 < ptr2;
    }
};

typedef function<void(void)> Function;

set<Function, Comparator<Function>> setOfFunc;

void f11() {}

int _tmain(int argc, _TCHAR* argv[])
{
    cout << "was inserted - " << setOfFunc.insert(bind(&f11)).second << endl;  // 1 - inserted
    cout << "was inserted - " << setOfFunc.insert(f11).second << endl;         // 0 - not inserted
    cout << "# of deleted is " << setOfFunc.erase(f11) << endl;

    return 0;
}

Ups、これは C++11 以降でのみ有効です。

于 2015-09-24T13:47:39.140 に答える
-1

次のようなことを試してみてはどうでしょうか。これはテンプレートのテストに適しています。

if (std::is_same<T1, T2>::value)
{
    ...
}
于 2015-09-24T20:19:23.933 に答える
-2

std::function が、文字列へのバインドに使用される関数のアドレスを保存し、代わりに文字列比較を使用する場合に、少なくとも実行できることです。

于 2011-06-27T04:57:51.403 に答える