简体   繁体   English

(C ++)用于检查对象是否在vector / array / list /…中的模板?

[英](C++) Template to check whether an object is in a vector/array/list/…?

Is it possible to create a template in C++(11) for a function to check whether an object is contained in either a std::vector , std::array or std::list (and possibly even more container types)? 是否可以在C ++(11)中为函数创建一个模板,以检查对象是否包含在std::vectorstd::arraystd::list (甚至可能还有更多的容器类型)中?

What I have by now: 我现在所拥有的:

typedef std::shared_ptr<Tag> SharedTag;
typedef std::vector<SharedTag> TagList;

bool
Tag::isIn(const TagList& lst) {
    return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
        return t->name == this->name;
    });
}

Tag is a normal class . Tag是普通class The comparison, of course, should be done t == this , which will be an operator== later on. 当然,应该进行比较t == this ,稍后将是一个operator== I did not include this here for simplicity. 为了简单起见,我没有在此包括。

So, is it possible to write the upper code only once (without the typedef's though,) for std::vector , std::array , std::list (, maybe for std::set ) and so on? 那么,是否有可能只为std::vectorstd::arraystd::list (也许对于std::set )只写一次(没有typedef的)上层代码?

I couldn't find a base-type of all these classes,... which would be my first idea... 我找不到所有这些类的基本类型,这是我的第一个主意。

Option 1 (good): just use std::find directly: 选项1(好):只需直接使用std::find即可:

std::vector<int> v; // populate v however you want
std::vector<int>::const_iterator i = std::find(v.cbegin(), v.cend(), 42);
if (i != v.end()) {
    // Now you know 42 is in v
} else {
    // Now you know 42 is not in v
}

Option 2 (better): wrap std::find in a helper function: 选项2(更好):将std::find包装在辅助函数中:

template <typename Container, typename Value>
bool contains(const Container& c, const Value& v)
{
    return std::find(std::begin(c), std::end(c), v) != std::begin(c);
}

// Example usage:
std::vector<int> v; // populate v however you want
if (contains(v, 42)) {
    // You now know v contains 42
}

Option 3 (best): use the find method of containers that provide one (which is faster for sorted containers, like set ), and std::find for containers that don't provide one: 选项3(最佳):使用提供一个的容器的find方法(对于排序的容器(例如set )更快),对于不提供一个的容器使用std::find

// If you want to know why I added the int and long parameter,
// see this answer here: http://stackoverflow.com/a/9154394/1287251

template <typename Container, typename Value>
inline auto contains(const Container& c, const Value& v, int) -> decltype(c.find(v), bool()) {
    return c.find(v) != std::end(c);
}

template <typename Container, typename Value>
inline bool contains(const Container& c, const Value& v, long) {
    return std::find(std::begin(c), std::end(c), v) != std::end(c);
}

template <typename Container, typename Value>
bool contains(const Container& c, const Value& v) {
    return contains(c, v, 0);
}

// Example usage:
std::set<int> s; // populate s however you want
if (contains(s, 42)) {
    // You now know s contains 42
}

Of course, you could write std::find yourself, but you might as well use it. 当然,您可以编写std::find自己,但也可以使用它。

You may use template: 您可以使用模板:

typedef std::shared_ptr<Tag> SharedTag;

template <typename Container>
bool Tag::isIn(const Container& lst) {
    return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
        return t->name == this->name;
    });
}

That requires that Container is a container of something convertible to SharedTag . 这要求Container是可转换为SharedTag的容器。

There is no common base-type between those containers. 这些容器之间没有通用的基本类型。 That's just not the way the STL library works, it is based on templates and generic programming principles. 这不是STL库的工作方式,它基于模板和通用编程原理。

So, if you want to implement the function once for all containers, you would have to make it a template. 因此,如果要对所有容器一次实现该功能,则必须使其成为模板。 Here is a basic form: 这是一个基本形式:

template <typename TagContainer>
bool Tag::isIn(const TagContainer& lst) {
  return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
    return t->name == this->name;
  });
};

But this has the problem that you could technically pass anything to this function that isn't actually a container of SharedTag , so, to solve this issue, you could use a trick called Sfinae to enforce that rule: 但这是一个问题,您可以在技术上将实际上不是SharedTag容器的任何内容传递给此函数,因此,要解决此问题,可以使用称为Sfinae的技巧来实施该规则:

template <typename TagContainer>
typename std::enable_if< std::is_same< SharedTag, typename TagContainer::value_type >::value,
bool >::type Tag::isIn(const TagContainer& lst) {
  return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
    return t->name == this->name;
  });
};

Which kind of ugly, but it works. 哪种丑陋,但是行得通。

There is still one problem though. 但是仍然存在一个问题。 I suspect that your Tag class is a normal non-template class, which means that you are probably implementing it in a cpp file, but templates need to be implemented in the header file (because function templates need to have their implementation visible to the compiler to generate a new concrete version of it for each type that you call it with). 我怀疑您的Tag类是普通的非模板类,这意味着您可能正在cpp文件中实现它,但是模板需要在头文件中实现(因为函数模板需要其实现对编译器可见为您使用的每种类型生成新的具体版本)。

One way to avoid this problem is to provide a number of overloaded non-template functions for each container you want to support, and then, under-the-hood, you call a local function template, and in this case, you don't need the sfinae trick to constrain it, since it is already limited to the set of overloads that you provided. 避免此问题的一种方法是为要支持的每个容器提供许多重载的非模板函数,然后在后台调用本地函数模板,在这种情况下,您不必需要sfinae技巧来约束它,因为它已经限于您提供的一组重载。 Something like this: 像这样:

template <typename TagContainer>
bool Tag::isIn_impl(const TagContainer& lst) {
  return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
    return t->name == this->name;
  });
};

bool Tag::isIn(const std::list<SharedTag>& lst) {
  return isIn_impl(lst);
};

bool Tag::isIn(const std::vector<SharedTag>& lst) {
  return isIn_impl(lst);
};

bool Tag::isIn(const std::set<SharedTag>& lst) {
  return isIn_impl(lst);
};

Note that the isIn_impl is a member function template that should be declared in the header file, in the private section of the class, and can safely be defined in the cpp file, because that cpp file is the only place where that function template is called from. 请注意, isIn_impl是成员函数模板,应在类的私有部分的头文件中声明该成员函数模板,并且可以安全地在cpp文件中定义该成员函数模板,因为该cpp文件是调用该函数模板的唯一位置从。

The obvious issue with that solution is that you have to manually provide every overload that you want to support, which means that it isn't very "scalable" in the future, but in real-life, there probably aren't that many containers that you'd want to support. 该解决方案的明显问题是,您必须手动提供要支持的每个重载,这意味着将来它不是非常“可扩展的”,但是在现实生活中,可能没有那么多容器您想要支持的。 If you want the full generality, you really have to use the template approach (unless you want to do type-erasure on the container... but that's a bit beyond the scope of what I'm willing to explain here). 如果您想获得全部通用性,则实际上必须使用模板方法(除非您要对容器进行类型擦除……但这超出了我在这里愿意解释的范围)。

You can use a nested variadic template to achieve this. 您可以使用嵌套的可变参数模板来实现此目的。 Here is a handy demo: note the magic part, template <template <typename...> class V, typename E> . 这是一个方便的演示:请注意魔术部分, template <template <typename...> class V, typename E> A variadic template is necessary because vector , list &co. 可变参数模板是必需的,因为vectorlist &co。 all have a different number of template parameters (allocator, comparator etc.) for which a default value is supplied by the STL. 它们都有不同数量的模板参数(分配器,比较器等),STL为此提供了默认值。

#include <vector>
#include <string>
#include <memory>
#include <algorithm>
#include <list>
#include <set>
#include <iostream>

class Tag {
public:
    Tag(const std::string &n): name(n) {}

    template <template <typename...> class V, typename E>
    bool isIn(const V<E> &lst) {
        return std::any_of(lst.begin(), lst.end(), [this](const E &t) {
            return t.name == this->name;
        });
    }

private:
    std::string name;
};

typedef std::shared_ptr<Tag> SharedTag;
typedef std::vector<SharedTag> TagList;

int main() {
    Tag t("foo");

    // Set needs some extra bits to work (a `<` operator etc.)
    //std::set<Tag> a = {Tag("foo"), Tag("bar")}; 
    std::vector<Tag> b = {Tag("foo"), Tag("bar")};           
    std::list<Tag> c = {Tag("foo"), Tag("bar")};

    //std::cout << t.isIn(a) << std::endl;
    std::cout << t.isIn(b) << std::endl;
    std::cout << t.isIn(c) << std::endl;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM