web-dev-qa-db-ja.com

なぜstd :: functionは同等ではないのですか?

この質問はboost::functionstd::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つの関数インスタンスを==または!=で比較できます。これらの未定義のvoid演算子は抜け穴を閉じ、コンパイル時エラーを確実にします。 — end note]

「抜け穴」についての私の理解は、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のセーフブールイディオムとC++ 11の明示的な変換関数の使用がこの「抜け穴」を回避するために使用されているという印象を受けました。 BoostとTR1はどちらもfunctionのセーフブールイディオムを使用し、C++ 11はbool変換関数を明示的にします。

両方を含むクラスの例として、std::shared_ptrは両方とも明示的なbool変換関数を持ち、同等であると同等です。

なぜstd::functionは同等性を比較できないのですか? 「型システムにあり得る穴」とは何ですか? std::shared_ptrとどう違うのですか?

61
James McNellis

なぜ std::function 同等性に匹敵しませんか?

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

「型システムにあり得る穴」とは何ですか?

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

std::shared_ptr

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

37
Mike Seymour

これは Boost.Function FAQ で徹底的に議論されています。 :-)

26

私は間違っているかもしれませんが、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...への呼び出しに要約される任意の数の関数オブジェクトを追加するとどうなりますか?

21
Evan Teran

なぜstd :: functionは同等ではないのですか?

主な理由は、もしそうなら、たとえ同等比較が決して実行されなくても、同等でない比較可能な型では使用できないということです。

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

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


compare boost :: function を呼び出し可能なオブジェクトと比較することは可能です(ただし、別のboost :: functionを使用することはできません)。

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

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

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

したがって、function_comparableの実装をブロックする技術的な障壁はありません。


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

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

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

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

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

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

基礎となるオブジェクトタイプが比較を定義しているが、正しく動作しない場合、または何らかの異常なセマンティクスがある場合-std :: function自体の問題でもありませんが、基礎となるタイプの問題です。


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

ここに概念実証があります:

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_comparablestd :: functionと比較できます。

たとえば、 vector of std :: function's があり、ユーザーに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
________________

追伸 std :: type_index の助けを借りて、function_comparableクラスと同様に実装することが可能であるように思われます。 。ただし、異なるタイプ間の順序付けだけでなく、同じタイプ内での順序付けも可能です(これにはLessThanComparableなどのタイプからのサポートが必要です)。

13
Evgeny Panasyuk

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

ここでの主要なコメントは、N1402で導入されたstd::functionの歴史の一部です。その間、明示的な変換関数は存在せず、「メンバーへのポインターに基づく」「セーフブール」イディオムが一般的な手法でした。このイディオムの唯一の欠点は、型std :: functionの2つのオブジェクトf1およびf2が指定されていることです。

f1 == f2;

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

C++ 0xでは、明示的な変換演算子が導入されているため、削除された関数は不必要であると見なされているため、C++ 0xではおそらく削除されます。

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

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

6
In silico

実際には、ターゲットを比較できます。それはあなたが比較から何を望んでいるかに依存して働くかもしれません。

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

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以降でのみ有効です。

4
Yola

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

if (std::is_same<T1, T2>::value)
{
    ...
}
0
Mercyful