【问题标题】:(C++) Template to check whether an object is in a vector/array/list/...?(C++) 检查对象是否在向量/数组/列表/...中的模板?
【发布时间】:2015-02-24 20:35:29
【问题描述】:

是否可以在 C++(11) 中为函数创建一个模板来检查对象是否包含在 std::vectorstd::arraystd::list(甚至可能更多的容器类型)中?

我现在拥有的:

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 是一个普通的class。当然,比较应该在t == this 进行,稍后将是operator==。为简单起见,我没有在此处包含此内容。

那么,是否可以只为std::vectorstd::arraystd::list(,也许是std::set) 等编写一次上层代码(尽管没有typedef)?

我找不到所有这些类的基本类型,...这是我的第一个想法...

【问题讨论】:

  • 你能用迭代器来写你的代码吗?有随机访问迭代器、前向迭代器等类别。
  • @NeilKirk:内部位已经做到了,他似乎只是想要一个包装函数

标签: c++ arrays c++11 vector stl


【解决方案1】:

选项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
}

选项 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
}

选项 3(最佳):对提供 1 的容器使用 find 方法(对于已排序的容器,这种方法更快,例如 set),对不提供 1 的容器使用 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
}

当然,你可以自己写std::find,但你也可以用它。

【讨论】:

    【解决方案2】:

    你可以使用模板:

    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;
        });
    }
    

    这要求 Container 是可转换为 SharedTag 的容器。

    【讨论】:

      【解决方案3】:

      这些容器之间没有共同的基本类型。这不是 STL 库的工作方式,它基于模板和通用编程原则。

      因此,如果您想为所有容器实现一次该功能,则必须将其设为模板。这是一个基本形式:

      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;
        });
      };
      

      但问题是,从技术上讲,您可以将任何实际上不是 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;
        });
      };
      

      哪种丑,但它有效。

      不过还有一个问题。我怀疑你的 Tag 类是一个普通的非模板类,这意味着你可能在 cpp 文件中实现它,但是模板需要在头文件中实现(因为函数模板需要它们的实现可见编译器为您调用它的每种类型生成一个新的具体版本)。

      避免此问题的一种方法是为您要支持的每个容器提供许多重载的非模板函数,然后在后台调用本地函数模板,在这种情况下,您不需要 sfinae 技巧来限制它,因为它已经限制在您提供的重载集内。像这样的:

      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);
      };
      

      请注意,isIn_impl 是一个成员函数模板,应该在头文件中,在类的私有部分中声明,并且可以安全地定义在 cpp 文件中,因为该 cpp 文件是调用该函数模板的唯一位置。

      该解决方案的明显问题是您必须手动提供要支持的每个重载,这意味着它在未来不是很“可扩展”,但在现实生活中,可能没有您想要支持的容器数量。如果你想要完整的通用性,你真的必须使用模板方法(除非你想对容器进行类型擦除......但这有点超出我愿意在这里解释的范围)。

      【讨论】:

      • 谢谢!这也是一个很好的解释!
      【解决方案4】:

      您可以使用嵌套的可变参数模板来实现此目的。这是一个方便的演示:注意神奇的部分,template &lt;template &lt;typename...&gt; class V, typename E&gt;。可变参数模板是必要的,因为vectorlist &co。它们都有不同数量的模板参数(分配器、比较器等),其默认值由 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;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-20
        • 1970-01-01
        • 2021-09-18
        相关资源
        最近更新 更多