为什么不能将std :: function用作std :: set或std :: unordered_set值类型?

时间:2018-11-24 15:34:13

标签: c++ unordered-map std-function stdset

为什么我不能有std::set的{​​{1}}或std::unordered_set

有什么办法使其正常工作吗?

4 个答案:

答案 0 :(得分:8)

您可以很好地创建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插入到一个集合中(因为它们始终是相同的结果),则它们的值应该相同吗?或者它是否有所不同(因为即使它们具有相同的主体,它们的功能也不同)?

当然,您可以欺骗编译器,让编译器认为您已定义了命令:

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对集合中的元素进行排序(或者在{{1的情况下进行哈希处理}})

答案 1 :(得分:3)

  

为什么我不能有std::set的{​​{1}}或std::unordered_set

std::function依赖于比较器,该比较器用于确定一个元素是否小于另一个元素。

默认情况下它使用std::set,而std::less不适用于std::less
(因为无法正确比较std::function。)

类似地,std::function依赖于std::unordered_setstd::hash(或它们的自定义替换),它们也不能在std::equal_to上运行。


  

有什么办法使其正常工作吗?

您可以为std::functionstd::function和/或std::less使用的std::equal_to包装(或替代)包装。

通过类型擦除的强大功能,您可以将std::hash / std::less / std::equal_to转发到包装器中存储的对象。

这是这种包装的概念证明。

注意:

  • 您可以通过调整模板参数来指定是否希望std::hashclass FancyFunctionstd::lessstd::equal_to一起使用。
    如果其中一些已启用,则可以将它们应用于std::hash

    自然地,只有将FancyFunction应用于该类型,您才能对其进行构造。

    有一个静态断言,当类型无法提供FancyFunction时触发该断言。
    对于SFINAE来说,std::hashstd::less的可用性似乎是不可能的,因此我无法对它们进行类似的断言。

  • 从理论上讲,您可以通过始终考虑同一种类型的所有实例并使用来支持与std::equal_tostd::less和/或std::equal_to不兼容的类型std::hash作为哈希值。

    我不确定该行为是否令人满意,将其实施留给读者练习。
    (缺少typeid(T).hash_code()std::less的SFINAE将使其难以正确实施。)

  • 不支持为std::equal_tostd::lessstd::equal_to指定自定义替换,实现这一点也留给读者练习。

    (这意味着该实现只能用于将lambda放入常规std::hash中,而不是std::set中。)

  • std::unordered_setFancyFunction应用于std::less时,将首先比较存储的函子的类型。

    如果类型相同,它们将诉诸于基础实例上的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.
     */
}

答案 2 :(得分:2)

那么,您只能检查功能指针是否(不相等),不能检查顺序。 And whether two functions with the same behavior must compare differently并不像您可能希望的那样干脆。

接下来,您不仅可以存储函数指针,还可以存储其他可调用对象。不能保证任何随机的用户定义类都具有严格的弱排序。例如,lambda不会。

最后,您将如何订购不同类型的可调用对象?

您可以为哈希(无序容器需要)和排序(有序容器需要)创建相同的参数。甚至不存在无序容器所需的相等比较。

答案 3 :(得分:1)

std::function对通用函数没有有意义的相等运算。

  • C ++函数不是数学函数。如果“功能”保持状态怎么办?那个状态对于不同的实例是不同的吗?
  • 对于使用“入口点地址”的建议:再次考虑状态。 std::function可以将某些函数/方法绑定到某些参数。那的“入口点地址”是什么?回答:“绑定”软件包中的某些功能/方法。然后,该“入口点地址”唯一地标识该功能吗?答:不。
  • 假设您有两个不同的函数(通过“入口点地址”),实际上它们在每个参数产生相同结果的意义上是相同的?它们甚至可能是相同的源代码-或机器代码。这些功能是否相等? (如果不是,为什么不这样做,如果它们在行为上是相同的,并且无法被任何呼叫者区分?)

您的特定用例(用于将std::function粘贴在一组中)可能不会受到上述问题的影响。在那种情况下,只需将std::function实例包装在您自己的小结构中(通过直接包含或通过间接)(将调用转发到所包含的函数对象),然后将这些内容放入您的集合中。