为什么set :: find不是模板?

时间:2012-06-16 20:38:27

标签: c++ stl c++11 std

使用<algorithm>中的模板函数,您可以执行此类操作

struct foo
{
    int bar, baz;
};

struct bar_less
{
    // compare foo with foo
    bool operator()(const foo& lh, const foo& rh) const
    {
        return lh.bar < rh.bar;
    }
    template<typename T>  // compare some T with foo
    bool operator()(T lh, const foo& rh) const
    {
        return lh < rh.bar;
    }
    template<typename T>  // compare foo with some T
    bool operator()(const foo& lh, T rh) const
    {
        return lh.bar < rh;
    }
};

int main()
{
    foo foos[] = { {1, 2}, {2, 3}, {4, 5} };
    bar_less cmp;
    int bar_value = 2;
    // find element {2, 3} using an int
    auto it = std::lower_bound(begin(foos), end(foos), bar_value, cmp);
    std::cout << it->baz;
}

在像std::set这样的find方法中,您必须传递set::key_type类型的对象,这通常会强制您创建虚拟对象。

set<foo> foos;
foo search_dummy = {2,3};  // don't need a full foo object;
auto it = foos.find(search_dummy);

如果只能拨打foos.find(2),那将会非常有用。有没有理由find不能成为模板,接受可以传递给较少谓词的所有内容。如果它只是缺失,为什么不在C ++ 11中(我认为它不是)。

修改

主要问题是为什么不可能,如果它是可行的,为什么决定不提供它的标准。第二个问题,你可以建议解决方法:-)(boost::multi_index_container刚刚过了我的脑海,它提供了从值类型的关键提取)

构造值类型更昂贵的另一个示例。键name是该类型的一部分,不应在map键中用作副本;

struct Person
{
    std::string name;
    std::string adress;
    std::string phone, email, fax, stackoferflowNickname;
    int age;
    std::vector<Person*> friends;
    std::vector<Relation> relations;
};

struct PersonOrder
{
    // assume that the full name is an unique identifier
    bool operator()(const Person& lh, const Person& rh) const
    {
        return lh.name < rh.name;
    }
};

class PersonRepository
{
public:

    const Person& FindPerson(const std::string& name) const
    {
        Person searchDummy;  // ouch
        searchDummy.name = name;
        return FindPerson(searchDummy);
    }

    const Person& FindPerson(const Person& person) const;

private:
    std::set<Person, PersonOrder> persons_;
    // what i want to avoid
    // std::map<std::string, Person> persons_;
    // Person searchDummyForReuseButNotThreadSafe;

};

5 个答案:

答案 0 :(得分:3)

std::find_if适用于未排序的范围。所以你可以传递你想要的任何谓词。

std::set<T>始终使用Comparator模板参数(默认情况下为std::less<T>)来维护集合的顺序,以及再次查找元素。

因此,如果std::set::find被模板化,则必须要求您只传递一个观察比较器总排序的谓词。

然后,std::lower_bound和所有其他适用于已排序范围的算法已经完全需要,因此这不是一个新的或令人惊讶的要求。

所以,我想这只是一个疏忽,find_if()上没有std::set(比如说​​)。建议用于C ++ 17 :)(编辑::EASTL already has this,他们使用了比我更好的名字:find_as)。

那就是说,你知道you shouldn't use std::set,是吗?在大多数情况下,排序后的矢量会更快,并且您可以获得std::set中缺少的灵活性。

编辑:正如Nicol所指出的那样,BoostLoki(以及其他地方,我确定)已经实现了这个概念,但是看到了因为你无法使用他们的主要优势(内置find()方法),所以使用裸std::vector你不会失去太多。

答案 1 :(得分:2)

标准规定std::set::find具有对数时间复杂度。在实践中,这是通过将std::set实现为二叉搜索树来实现的,其中strict weak ordering比较用作排序标准。任何不满足相同严格弱排序标准的查找都不会满足对数复杂度。因此,这是一个设计决策:如果您想要对数复杂度,请使用std::set::find。如果您可以交换复杂性以获得灵活性,请在集合上使用std::find_if

答案 2 :(得分:1)

他们提供了你想要的东西,但却与你正在考虑的方式截然不同。

实际上,有两种不同的方式:一种是为包含的类构建一个构造函数,1)可以隐式使用,2)只需要您真正需要进行比较的元素子集。有了这个,您可以搜索foods.find(2);。你最终从2创建一个临时对象,然后找到那个临时对象它将是一个真正的临时对象。您的代码不必明确(任何地方)处理它。

编辑:我在这里谈论的是创建一个与你在地图中存储相同类型的实例,但是(可能)将你没有使用的任何一个字段作为“密钥”未初始化(或初始化为“不存在”的东西)。例如:

struct X { 
   int a; // will be treated as the key
   std:::string data;
   std::vector<int> more_data;
public:
   X(int a) : a(a) {} // the "key-only" ctor
   X(int a, std::string const &d, std::vector<int> const &m); // the normal ctor
};

std::set<X> s;

if (s.find(2)) { // will use X::X(int) to construct an `X`
    // we've found what we were looking for
}

是的,当你用单参数构造函数构造你的X(或者我称之为X的东西)时,很可能你构造的东西除了搜索之外什么都不可用。

结束编辑]

第二个,库为其提供更直接的支持通常更简单:如果您实际上只使用某些元素子集(可能只有一个)进行搜索,那么您可以创建std::map代替std::set。使用std::map,显式/直接支持搜索您指定为密钥类型的任何实例。

答案 3 :(得分:0)

  

key_type是在set容器中定义的成员类型,作为Key的别名,它是第一个模板参数和容器中存储的元素的类型。

请参阅documentation

对于用户定义的类型,库无法知道密钥类型是什么。碰巧的是,对于您的特定用例密钥类型为int。如果您使用set< int > s;,则可以致电s.find( 2 );。但是,如果要搜索set< foo >并且只想传入一个整数,则需要帮助编译器(考虑set的排序如何在foo之间工作int)。

答案 4 :(得分:0)

因为如果你想做std::find(2),除了两个int之间的比较之外,你还必须定义foofoo的比较方式。但由于intfoo是不同的类型,因此您实际上需要另外两个函数:

bool operator<(int, foo);
bool operator<(foo, int);

(或其他一些逻辑等效对)。

现在,如果你那么,你实际上是在intfoo之间定义了一个双射函数,你也可以简单地使用std::map<int, foo>并完成。

如果你仍然不想要std::map,但是你想要排序搜索的好处,那么虚拟对象实际上是最简单的方法。

当然,标准std::set可以提供一个成员函数,你传递一个接收foo的函数并返回-1,0,1,如果它小于,等于或大于搜索了一个...但这不是C ++方式。请注意,即使bsearch()也需要两个相同类型的参数!