簡體   English   中英

為什么我不能將std :: function用作std :: set或std :: unordered_set值類型?

[英]Why can't I use std::function as a std::set or std::unordered_set value type?

為什么我不能擁有std::function s的std::setstd::unordered_set

有什么方法可以讓它工作嗎?

你可以很好地創建一個std::set函數std::set 問題是集合要求在其元素的值之間存在絕對順序。 此順序由比較器定義,然后比較器用於對集合的元素進行排序,檢查元素是否已存在,以及返回特定元素。

不幸的是,函數之間不存在順序。 假設你有兩個函數f1()f2()f1 < f2的含義是什么?

也沒有真正定義平等。 例如,如果你有

int fun1(int) { return 1; }
int fun2(int) { return 1; }
function<int(int)> f1=fun1, f2=fun2; 

如果你將f1和f2插入一個集合(因為它總是相同的結果),或者它是不同的東西(因為即使它們具有相同的主體,它是不同的功能),f1和f2應該是相同的值嗎?

當然,您可以欺騙編譯器讓它相信您已經定義了一個訂單:

struct Comp {
    using T = function<int(int)>;
    bool operator()(const T &lhs, const T &rhs) const 
    {
        return &lhs < &rhs;
    }
};

set <function<int(int)>,Comp> s; 

然后,您可以在集合中插入函數。 但這不會很好,因為你獲取元素的地址,如果交換相同的元素,順序是不同的。

我認為最好的方法是使用包含定義id的成員字符串的包裝器,並使用此id對集合中的元素進行排序(或者在unordered_set情況下進行散列)

為什么我不能擁有std::function s的std::setstd::unordered_set

std::set依賴於比較器,用於確定一個元素是否小於另一個元素。

它默認使用std::less ,而std::less不能用於std::function
(因為沒有辦法正確比較std::function s。)

類似地, std::unordered_set依賴於std::hashstd::equal_to (或者它們的自定義替換),它們也不在std::function s上運行。


有什么方法可以讓它工作嗎?

您可以使用std::lessstd::equal_to和/或std::hash編寫一個包裝(或替換) std::function的包裝器。

通過類型擦除的強大功能,您可以將std::less / std::equal_to / std::hash std::equal_to到存儲在包裝器中的對象。

這是包裝器的概念驗證。

筆記:

  • 您可以通過調整模板參數來指定是否希望class FancyFunctionstd::lessstd::equal_tostd::hash separetely一起使用。
    如果其中一些已啟用,您將能夠將它們應用於FancyFunction

    當然,只有當它們可以應用於該類型時,您才能夠從類型構造FancyFunction

    當一個類型在需要時無法提供std::hash時會觸發一個靜態斷言。
    對於std::lessstd::equal_to可用性,SFINAE似乎是不可能的,所以我不能為那些做出類似的斷言。

  • 從理論上講,你可以通過總是考慮一個等價的所有實例,並使用typeid(T).hash_code()來支持不能用於std::lessstd::equal_to和/或std::hash的類型。哈希。

    我不確定這種行為是否可取,實施它是留給讀者的練習。
    (對於std::lessstd::equal_to缺少SFINAE會使正確實現更難。)

  • 不支持為std::lessstd::equal_tostd::hash指定自定義替換,實現它也是為讀者留下的練習。

    (這意味着此實現只能用於將lambdas放入常規std::set ,而不是std::unordered_set 。)

  • 當應用於FancyFunctionstd::lessstd::equal_to將首先比較存儲的std::equal_to函數的類型。

    如果類型相同,他們將在底層實例上調用std::less / std::equal_to

    (因此,對於兩個任意不同的函子類型, std::less將始終認為其中一個的實例少於另一個的實例。程序調用之間的順序不穩定。)

用法示例:

// With `std::set`:

#include <iostream>
#include <set>

struct AddN
{
    int n;
    int operator()(int x) const {return n + x;}
    friend bool operator<(AddN a, AddN b) {return a.n < b.n;}
};

int main()
{   
    using func_t = FancyFunction<int(int), FunctionFlags::comparable_less>;

    // Note that `std::less` can operate on stateless lambdas by converting them to function pointers first. Otherwise this wouldn't work.
    auto square = [](int x){return x*x;};
    auto cube = [](int x){return x*x*x;};

    std::set<func_t> set;
    set.insert(square);
    set.insert(square); // Dupe.
    set.insert(cube);
    set.insert(AddN{100});
    set.insert(AddN{200});
    set.insert(AddN{200}); // Dupe.

    for (const auto &it : set)
        std::cout << "2 -> " << it(2) << '\n';
    std::cout << '\n';
    /* Prints:
     * 2 -> 4   // `square`, note that it appears only once.
     * 2 -> 8   // `cube`
     * 2 -> 102 // `AddN{100}`
     * 2 -> 202 // `AddN{200}`, also appears once.
     */

    set.erase(set.find(cube));
    set.erase(set.find(AddN{100}));

    for (const auto &it : set)
        std::cout << "2 -> " << it(2) << '\n';
    std::cout << '\n';
    /* Prints:
     * 2 -> 4   // `square`
     * 2 -> 202 // `AddN{200}`
     * `cube` and `AddN{100}` were removed.
     */
}


// With `std::unordered_set`:

#include <iostream>
#include <unordered_set>

struct AddN
{
    int n;
    int operator()(int x) const {return n + x;}
    friend bool operator==(AddN a, AddN b) {return a.n == b.n;}
};

struct MulByN
{
    int n;
    int operator()(int x) const {return n * x;}
    friend bool operator==(MulByN a, MulByN b) {return a.n == b.n;}
};

namespace std
{
    template <> struct hash<AddN>
    {
        using argument_type = AddN;
        using result_type = std::size_t;
        size_t operator()(AddN f) const {return f.n;}
    };

    template <> struct hash<MulByN>
    {
        using argument_type = MulByN;
        using result_type = std::size_t;
        size_t operator()(MulByN f) const {return f.n;}
    };
}

int main()
{   
    using hashable_func_t = FancyFunction<int(int), FunctionFlags::hashable | FunctionFlags::comparable_eq>;
    std::unordered_set<hashable_func_t> set;
    set.insert(AddN{100});
    set.insert(AddN{100}); // Dupe.
    set.insert(AddN{200});
    set.insert(MulByN{10});
    set.insert(MulByN{20});
    set.insert(MulByN{20}); // Dupe.

    for (const auto &it : set)
        std::cout << "2 -> " << it(2) << '\n';
    std::cout << '\n';
    /* Prints:
     * 2 -> 40  // `MulByN{20}`
     * 2 -> 20  // `MulByN{10}`
     * 2 -> 102 // `AddN{100}`
     * 2 -> 202 // `AddN{200}`
     */

    set.erase(set.find(AddN{100}));
    set.erase(set.find(MulByN{20}));

    for (const auto &it : set)
        std::cout << "2 -> " << it(2) << '\n';
    std::cout << '\n';
    /* Prints:
     * 2 -> 20  // `MulByN{10}`
     * 2 -> 202 // `AddN{200}`
     * `MulByN{20}` and `AddN{100}` were removed.
     */
}

執行:

#include <cstddef>
#include <functional>
#include <experimental/type_traits>
#include <utility>

enum class FunctionFlags
{
    none            = 0,
    comparable_less = 0b1,
    comparable_eq   = 0b10,
    hashable        = 0b100,
};
constexpr FunctionFlags operator|(FunctionFlags a, FunctionFlags b) {return FunctionFlags(int(a) | int(b));}
constexpr FunctionFlags operator&(FunctionFlags a, FunctionFlags b) {return FunctionFlags(int(a) & int(b));}


template <typename T> using detect_hashable = decltype(std::hash<T>{}(std::declval<const T &>()));


template <typename T, FunctionFlags Flags = FunctionFlags::none>
class FancyFunction;

template <typename ReturnType, typename ...ParamTypes, FunctionFlags Flags>
class FancyFunction<ReturnType(ParamTypes...), Flags>
{
    struct TypeDetails
    {
        int index = 0;
        bool (*less)(const void *, const void *) = 0;
        bool (*eq)(const void *, const void *) = 0;
        std::size_t (*hash)(const void *) = 0;

        inline static int index_counter = 0;
    };

    template <typename T> const TypeDetails *GetDetails()
    {
        static TypeDetails ret = []()
        {
            using type = std::remove_cv_t<std::remove_reference_t<T>>;

            TypeDetails d;

            d.index = TypeDetails::index_counter++;

            if constexpr (comparable_less)
            {
                // We can't SFINAE on `std::less`.
                d.less = [](const void *a_ptr, const void *b_ptr) -> bool
                {
                    const type &a = *static_cast<const FancyFunction *>(a_ptr)->func.template target<type>();
                    const type &b = *static_cast<const FancyFunction *>(b_ptr)->func.template target<type>();
                    return std::less<type>{}(a, b);
                };
            }

            if constexpr (comparable_eq)
            {
                // We can't SFINAE on `std::equal_to`.
                d.eq = [](const void *a_ptr, const void *b_ptr) -> bool
                {
                    const type &a = *static_cast<const FancyFunction *>(a_ptr)->func.template target<type>();
                    const type &b = *static_cast<const FancyFunction *>(b_ptr)->func.template target<type>();
                    return std::equal_to<type>{}(a, b);
                };
            }

            if constexpr (hashable)
            {
                static_assert(std::experimental::is_detected_v<detect_hashable, type>, "This type is not hashable.");
                d.hash = [](const void *a_ptr) -> std::size_t
                {
                    const type &a = *static_cast<const FancyFunction *>(a_ptr)->func.template target<type>();
                    return std::hash<type>(a);
                };
            }

            return d;
        }();
        return &ret;
    }

    std::function<ReturnType(ParamTypes...)> func;
    const TypeDetails *details = 0;

  public:
    inline static constexpr bool
        comparable_less = bool(Flags & FunctionFlags::comparable_less),
        comparable_eq   = bool(Flags & FunctionFlags::comparable_eq),
        hashable        = bool(Flags & FunctionFlags::hashable);

    FancyFunction(decltype(nullptr) = nullptr) {}

    template <typename T>
    FancyFunction(T &&obj)
    {
        func = std::forward<T>(obj);    
        details = GetDetails<T>();
    }

    explicit operator bool() const
    {
        return bool(func);
    }

    ReturnType operator()(ParamTypes ... params) const
    {
        return ReturnType(func(std::forward<ParamTypes>(params)...));
    }

    bool less(const FancyFunction &other) const
    {
        static_assert(comparable_less, "This function is disabled.");
        if (int delta = bool(details) - bool(other.details)) return delta < 0;
        if (!details) return 0;
        if (int delta = details->index - other.details->index) return delta < 0;
        return details->less(this, &other);
    }

    bool equal_to(const FancyFunction &other) const
    {
        static_assert(comparable_eq, "This function is disabled.");
        if (bool(details) != bool(other.details)) return 0;
        if (!details) return 1;
        if (details->index != other.details->index) return 0;
        return details->eq(this, &other);
    }

    std::size_t hash() const
    {
        static_assert(hashable, "This function is disabled.");
        if (!details) return 0;
        return details->hash(this);
    }

    friend bool operator<(const FancyFunction &a, const FancyFunction &b) {return a.less(b);}
    friend bool operator>(const FancyFunction &a, const FancyFunction &b) {return b.less(a);}
    friend bool operator<=(const FancyFunction &a, const FancyFunction &b) {return !b.less(a);}
    friend bool operator>=(const FancyFunction &a, const FancyFunction &b) {return !a.less(b);}
    friend bool operator==(const FancyFunction &a, const FancyFunction &b) {return a.equal_to(b);}
    friend bool operator!=(const FancyFunction &a, const FancyFunction &b) {return !a.equal_to(b);}
};

namespace std
{
    template <typename T, FunctionFlags Flags> struct hash<FancyFunction<T, Flags>>
    {
        using argument_type = FancyFunction<T, Flags>;
        using result_type = std::size_t;
        size_t operator()(const FancyFunction<T, Flags> &f) const
        {
            return f.hash();
        }
    };
}

那么,你只能檢查(in-)相等的函數指針,而不是順序。 並且具有相同行為的兩個函數是否必須進行不同的比較並不像您可能希望的那樣切割干燥。

接下來,您可能不僅存儲函數指針,還存儲其他callables。 無法保證任何隨機用戶定義的類具有嚴格的弱排序。 舉個例子,lambdas沒有。

最后,你會如何訂購不同類型的callables?

您可以為散列(無序容器所需)和訂購(需要的有序容器)制作相同的參數。 即使是無序容器所需的相等比較也可能不存在。

std::function持有的一般函數沒有有意義的相等操作。

  • C ++函數不是數學函數。 如果“功能”保持狀態怎么辦? 對於不同的情況,這種狀態是不同的?
  • 對於您使用“入口點地址”的建議:再次考慮狀態。 std::function可以將某些函數/方法綁定到某些參數。 那個“切入點地址”是什么? Ans:“bind”包中的一些函數/方法。 那么“入口點地址”是否唯一地標識了該功能? 答:沒有。
  • 假設你有兩個不同的函數(通過“入口點地址”)實際上是相同的,因為它們為每個參數產生相同的結果? 它們甚至可能是相同的源代碼 - 或機器代碼。 這些功能是否相同? (如果不是,為什么不,如果它們在行為上相同並且無法被任何來電者區分?)

您的特定用例(用於在一組中粘貼std::function )可能不會受到上述問題的影響。 在這種情況下,只需將std::function實例包裝在您自己的小結構中(通過直接包含或通過間接)(轉發對包含的函數對象的調用)並將這些內容放入您的集合中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM